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);
1553 if (item && !item->path && root_folder) {
1554 item->path = g_strdup(root_folder);
1557 if (!item || ((item->path || root_folder) &&
1558 strcmp2(item->path, root_folder) != 0)) {
1559 folder_tree_destroy(folder);
1560 item = folder_item_new(folder, folder->name, root_folder);
1561 item->folder = folder;
1562 folder->node = item->node = g_node_new(item);
1565 imap_scan_tree_recursive(session, FOLDER_ITEM(folder->node->data));
1566 imap_create_missing_folders(folder);
1572 static gint imap_scan_tree_recursive(IMAPSession *session, FolderItem *item)
1575 IMAPFolder *imapfolder;
1576 FolderItem *new_item;
1577 GSList *item_list, *cur;
1580 gchar *wildcard_path;
1586 g_return_val_if_fail(item != NULL, -1);
1587 g_return_val_if_fail(item->folder != NULL, -1);
1588 g_return_val_if_fail(item->no_sub == FALSE, -1);
1590 folder = item->folder;
1591 imapfolder = IMAP_FOLDER(folder);
1593 separator = imap_get_path_separator(session, imapfolder, item->path);
1595 if (folder->ui_func)
1596 folder->ui_func(folder, item, folder->ui_func_data);
1599 wildcard[0] = separator;
1602 real_path = imap_get_real_path(session, imapfolder, item->path);
1606 real_path = g_strdup("");
1609 Xstrcat_a(wildcard_path, real_path, wildcard,
1610 {g_free(real_path); return IMAP_ERROR;});
1612 r = imap_threaded_list(folder, "", wildcard_path, &lep_list);
1613 if (r != MAILIMAP_NO_ERROR) {
1617 item_list = imap_list_from_lep(imapfolder,
1618 lep_list, real_path, FALSE);
1619 mailimap_list_result_free(lep_list);
1624 node = item->node->children;
1625 while (node != NULL) {
1626 FolderItem *old_item = FOLDER_ITEM(node->data);
1627 GNode *next = node->next;
1630 for (cur = item_list; cur != NULL; cur = cur->next) {
1631 FolderItem *cur_item = FOLDER_ITEM(cur->data);
1632 if (!strcmp2(old_item->path, cur_item->path)) {
1633 new_item = cur_item;
1638 if (old_item && old_item->path && !strcmp(old_item->path, "INBOX")) {
1639 debug_print("not removing INBOX\n");
1641 debug_print("folder '%s' not found. removing...\n",
1643 folder_item_remove(old_item);
1646 old_item->no_sub = new_item->no_sub;
1647 old_item->no_select = new_item->no_select;
1648 if (old_item->no_sub == TRUE && node->children) {
1649 debug_print("folder '%s' doesn't have "
1650 "subfolders. removing...\n",
1652 folder_item_remove_children(old_item);
1659 for (cur = item_list; cur != NULL; cur = cur->next) {
1660 FolderItem *cur_item = FOLDER_ITEM(cur->data);
1663 for (node = item->node->children; node != NULL;
1664 node = node->next) {
1665 if (!strcmp2(FOLDER_ITEM(node->data)->path,
1667 new_item = FOLDER_ITEM(node->data);
1668 folder_item_destroy(cur_item);
1674 new_item = cur_item;
1675 debug_print("new folder '%s' found.\n", new_item->path);
1676 folder_item_append(item, new_item);
1679 if (!strcmp(new_item->path, "INBOX")) {
1680 new_item->stype = F_INBOX;
1681 folder->inbox = new_item;
1682 } else if (!folder_item_parent(item) || item->stype == F_INBOX) {
1685 base = g_path_get_basename(new_item->path);
1687 if (!folder->outbox && !g_ascii_strcasecmp(base, "Sent")) {
1688 new_item->stype = F_OUTBOX;
1689 folder->outbox = new_item;
1690 } else if (!folder->draft && !g_ascii_strcasecmp(base, "Drafts")) {
1691 new_item->stype = F_DRAFT;
1692 folder->draft = new_item;
1693 } else if (!folder->queue && !g_ascii_strcasecmp(base, "Queue")) {
1694 new_item->stype = F_QUEUE;
1695 folder->queue = new_item;
1696 } else if (!folder->trash && !g_ascii_strcasecmp(base, "Trash")) {
1697 new_item->stype = F_TRASH;
1698 folder->trash = new_item;
1703 if (new_item->no_sub == FALSE)
1704 imap_scan_tree_recursive(session, new_item);
1707 g_slist_free(item_list);
1709 return IMAP_SUCCESS;
1712 static gint imap_create_tree(Folder *folder)
1714 g_return_val_if_fail(folder != NULL, -1);
1715 g_return_val_if_fail(folder->node != NULL, -1);
1716 g_return_val_if_fail(folder->node->data != NULL, -1);
1717 g_return_val_if_fail(folder->account != NULL, -1);
1719 imap_scan_tree(folder);
1720 imap_create_missing_folders(folder);
1725 static void imap_create_missing_folders(Folder *folder)
1727 g_return_if_fail(folder != NULL);
1730 folder->inbox = imap_create_special_folder
1731 (folder, F_INBOX, "INBOX");
1733 folder->trash = imap_create_special_folder
1734 (folder, F_TRASH, "Trash");
1736 folder->queue = imap_create_special_folder
1737 (folder, F_QUEUE, "Queue");
1738 if (!folder->outbox)
1739 folder->outbox = imap_create_special_folder
1740 (folder, F_OUTBOX, "Sent");
1742 folder->draft = imap_create_special_folder
1743 (folder, F_DRAFT, "Drafts");
1746 static FolderItem *imap_create_special_folder(Folder *folder,
1747 SpecialFolderItemType stype,
1751 FolderItem *new_item;
1753 g_return_val_if_fail(folder != NULL, NULL);
1754 g_return_val_if_fail(folder->node != NULL, NULL);
1755 g_return_val_if_fail(folder->node->data != NULL, NULL);
1756 g_return_val_if_fail(folder->account != NULL, NULL);
1757 g_return_val_if_fail(name != NULL, NULL);
1759 item = FOLDER_ITEM(folder->node->data);
1760 new_item = imap_create_folder(folder, item, name);
1763 g_warning("Can't create '%s'\n", name);
1764 if (!folder->inbox) return NULL;
1766 new_item = imap_create_folder(folder, folder->inbox, name);
1768 g_warning("Can't create '%s' under INBOX\n", name);
1770 new_item->stype = stype;
1772 new_item->stype = stype;
1777 static gchar *imap_folder_get_path(Folder *folder)
1781 g_return_val_if_fail(folder != NULL, NULL);
1782 g_return_val_if_fail(folder->account != NULL, NULL);
1784 folder_path = g_strconcat(get_imap_cache_dir(),
1786 folder->account->recv_server,
1788 folder->account->userid,
1794 static gchar *imap_item_get_path(Folder *folder, FolderItem *item)
1796 gchar *folder_path, *path;
1798 g_return_val_if_fail(folder != NULL, NULL);
1799 g_return_val_if_fail(item != NULL, NULL);
1800 folder_path = imap_folder_get_path(folder);
1802 g_return_val_if_fail(folder_path != NULL, NULL);
1803 if (folder_path[0] == G_DIR_SEPARATOR) {
1805 path = g_strconcat(folder_path, G_DIR_SEPARATOR_S,
1808 path = g_strdup(folder_path);
1811 path = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S,
1812 folder_path, G_DIR_SEPARATOR_S,
1815 path = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S,
1818 g_free(folder_path);
1823 static FolderItem *imap_create_folder(Folder *folder, FolderItem *parent,
1826 gchar *dirpath, *imap_path;
1827 IMAPSession *session;
1828 FolderItem *new_item;
1833 gboolean no_select = FALSE, no_sub = FALSE;
1835 g_return_val_if_fail(folder != NULL, NULL);
1836 g_return_val_if_fail(folder->account != NULL, NULL);
1837 g_return_val_if_fail(parent != NULL, NULL);
1838 g_return_val_if_fail(name != NULL, NULL);
1840 debug_print("getting session...\n");
1841 session = imap_session_get(folder);
1846 if (!folder_item_parent(parent) && strcmp(name, "INBOX") == 0) {
1847 dirpath = g_strdup(name);
1848 }else if (parent->path)
1849 dirpath = g_strconcat(parent->path, "/", name, NULL);
1850 else if ((p = strchr(name, '/')) != NULL && *(p + 1) != '\0')
1851 dirpath = g_strdup(name);
1852 else if (folder->account->imap_dir && *folder->account->imap_dir) {
1855 Xstrdup_a(imap_dir, folder->account->imap_dir, {unlock_session();return NULL;});
1856 strtailchomp(imap_dir, '/');
1857 dirpath = g_strconcat(imap_dir, "/", name, NULL);
1859 dirpath = g_strdup(name);
1863 /* keep trailing directory separator to create a folder that contains
1865 imap_path = imap_utf8_to_modified_utf7(dirpath);
1867 strtailchomp(dirpath, '/');
1868 Xstrdup_a(new_name, name, {
1873 separator = imap_get_path_separator(session, IMAP_FOLDER(folder), imap_path);
1874 imap_path_separator_subst(imap_path, separator);
1875 /* remove trailing / for display */
1876 strtailchomp(new_name, '/');
1878 if (strcmp(dirpath, "INBOX") != 0) {
1880 gboolean exist = FALSE;
1884 argbuf = g_ptr_array_new();
1885 r = imap_threaded_list(folder, "", imap_path, &lep_list);
1886 if (r != MAILIMAP_NO_ERROR) {
1887 log_warning(_("can't create mailbox: LIST failed\n"));
1890 ptr_array_free_strings(argbuf);
1891 g_ptr_array_free(argbuf, TRUE);
1896 if (clist_count(lep_list) > 0)
1898 mailimap_list_result_free(lep_list);
1901 ok = imap_cmd_create(session, imap_path);
1902 if (ok != IMAP_SUCCESS) {
1903 log_warning(_("can't create mailbox\n"));
1909 r = imap_threaded_list(folder, "", imap_path, &lep_list);
1910 if (r == MAILIMAP_NO_ERROR) {
1911 GSList *item_list = imap_list_from_lep(IMAP_FOLDER(folder),
1912 lep_list, dirpath, TRUE);
1914 FolderItem *cur_item = FOLDER_ITEM(item_list->data);
1915 no_select = cur_item->no_select;
1916 no_sub = cur_item->no_sub;
1917 g_slist_free(item_list);
1919 mailimap_list_result_free(lep_list);
1926 /* just get flags */
1927 r = imap_threaded_list(folder, "", "INBOX", &lep_list);
1928 if (r == MAILIMAP_NO_ERROR) {
1929 GSList *item_list = imap_list_from_lep(IMAP_FOLDER(folder),
1930 lep_list, dirpath, TRUE);
1932 FolderItem *cur_item = FOLDER_ITEM(item_list->data);
1933 no_select = cur_item->no_select;
1934 no_sub = cur_item->no_sub;
1935 g_slist_free(item_list);
1937 mailimap_list_result_free(lep_list);
1941 new_item = folder_item_new(folder, new_name, dirpath);
1942 new_item->no_select = no_select;
1943 new_item->no_sub = no_sub;
1944 folder_item_append(parent, new_item);
1948 dirpath = folder_item_get_path(new_item);
1949 if (!is_dir_exist(dirpath))
1950 make_dir_hier(dirpath);
1956 static gint imap_rename_folder(Folder *folder, FolderItem *item,
1961 gchar *real_oldpath;
1962 gchar *real_newpath;
1964 gchar *old_cache_dir;
1965 gchar *new_cache_dir;
1966 IMAPSession *session;
1969 gint exists, recent, unseen;
1970 guint32 uid_validity;
1972 g_return_val_if_fail(folder != NULL, -1);
1973 g_return_val_if_fail(item != NULL, -1);
1974 g_return_val_if_fail(item->path != NULL, -1);
1975 g_return_val_if_fail(name != NULL, -1);
1977 debug_print("getting session...\n");
1978 session = imap_session_get(folder);
1983 if (strchr(name, imap_get_path_separator(session, IMAP_FOLDER(folder), item->path)) != NULL) {
1984 g_warning(_("New folder name must not contain the namespace "
1990 real_oldpath = imap_get_real_path(session, IMAP_FOLDER(folder), item->path);
1992 g_free(session->mbox);
1993 session->mbox = NULL;
1994 ok = imap_cmd_examine(session, "INBOX",
1995 &exists, &recent, &unseen, &uid_validity, FALSE);
1996 if (ok != IMAP_SUCCESS) {
1997 g_free(real_oldpath);
2002 separator = imap_get_path_separator(session, IMAP_FOLDER(folder), item->path);
2003 if (strchr(item->path, G_DIR_SEPARATOR)) {
2004 dirpath = g_path_get_dirname(item->path);
2005 newpath = g_strconcat(dirpath, G_DIR_SEPARATOR_S, name, NULL);
2008 newpath = g_strdup(name);
2010 real_newpath = imap_utf8_to_modified_utf7(newpath);
2011 imap_path_separator_subst(real_newpath, separator);
2013 ok = imap_cmd_rename(session, real_oldpath, real_newpath);
2014 if (ok != IMAP_SUCCESS) {
2015 log_warning(_("can't rename mailbox: %s to %s\n"),
2016 real_oldpath, real_newpath);
2017 g_free(real_oldpath);
2019 g_free(real_newpath);
2025 item->name = g_strdup(name);
2027 old_cache_dir = folder_item_get_path(item);
2029 paths[0] = g_strdup(item->path);
2031 g_node_traverse(item->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
2032 imap_rename_folder_func, paths);
2034 if (is_dir_exist(old_cache_dir)) {
2035 new_cache_dir = folder_item_get_path(item);
2036 if (rename(old_cache_dir, new_cache_dir) < 0) {
2037 FILE_OP_ERROR(old_cache_dir, "rename");
2039 g_free(new_cache_dir);
2042 g_free(old_cache_dir);
2045 g_free(real_oldpath);
2046 g_free(real_newpath);
2051 static gint imap_remove_folder_real(Folder *folder, FolderItem *item)
2054 IMAPSession *session;
2058 g_return_val_if_fail(folder != NULL, -1);
2059 g_return_val_if_fail(item != NULL, -1);
2060 g_return_val_if_fail(item->path != NULL, -1);
2062 debug_print("getting session...\n");
2063 session = imap_session_get(folder);
2067 path = imap_get_real_path(session, IMAP_FOLDER(folder), item->path);
2069 ok = imap_cmd_delete(session, path);
2070 if (ok != IMAP_SUCCESS) {
2071 gchar *tmp = g_strdup_printf("%s%c", path,
2072 imap_get_path_separator(session, IMAP_FOLDER(folder), path));
2075 ok = imap_cmd_delete(session, path);
2078 if (ok != IMAP_SUCCESS) {
2079 log_warning(_("can't delete mailbox\n"));
2086 cache_dir = folder_item_get_path(item);
2087 if (is_dir_exist(cache_dir) && remove_dir_recursive(cache_dir) < 0)
2088 g_warning("can't remove directory '%s'\n", cache_dir);
2090 folder_item_remove(item);
2095 static gint imap_remove_folder(Folder *folder, FolderItem *item)
2099 g_return_val_if_fail(item != NULL, -1);
2100 g_return_val_if_fail(item->folder != NULL, -1);
2101 g_return_val_if_fail(item->node != NULL, -1);
2103 node = item->node->children;
2104 while (node != NULL) {
2106 if (imap_remove_folder(folder, FOLDER_ITEM(node->data)) < 0)
2110 debug_print("IMAP removing %s\n", item->path);
2112 if (imap_remove_all_msg(folder, item) < 0)
2114 return imap_remove_folder_real(folder, item);
2117 typedef struct _uncached_data {
2118 IMAPSession *session;
2120 MsgNumberList *numlist;
2126 static void *imap_get_uncached_messages_thread(void *data)
2128 uncached_data *stuff = (uncached_data *)data;
2129 IMAPSession *session = stuff->session;
2130 FolderItem *item = stuff->item;
2131 MsgNumberList *numlist = stuff->numlist;
2133 GSList *newlist = NULL;
2134 GSList *llast = NULL;
2135 GSList *seq_list, *cur;
2137 debug_print("uncached_messages\n");
2139 if (session == NULL || item == NULL || item->folder == NULL
2140 || FOLDER_CLASS(item->folder) != &imap_class) {
2145 seq_list = imap_get_lep_set_from_numlist(numlist);
2146 debug_print("get msgs info\n");
2147 for (cur = seq_list; cur != NULL; cur = g_slist_next(cur)) {
2148 struct mailimap_set * imapset;
2154 imapset = cur->data;
2156 r = imap_threaded_fetch_env(session->folder,
2157 imapset, &env_list);
2158 if (r != MAILIMAP_NO_ERROR)
2161 session_set_access_time(SESSION(session));
2164 for(i = 0 ; i < carray_count(env_list) ; i ++) {
2165 struct imap_fetch_env_info * info;
2168 info = carray_get(env_list, i);
2169 msginfo = imap_envelope_from_lep(info, item);
2170 if (msginfo == NULL)
2172 msginfo->folder = item;
2174 llast = newlist = g_slist_append(newlist, msginfo);
2176 llast = g_slist_append(llast, msginfo);
2177 llast = llast->next;
2182 imap_fetch_env_free(env_list);
2185 for (cur = seq_list; cur != NULL; cur = g_slist_next(cur)) {
2186 struct mailimap_set * imapset;
2188 imapset = cur->data;
2189 mailimap_set_free(imapset);
2192 session_set_access_time(SESSION(session));
2197 #define MAX_MSG_NUM 50
2199 static GSList *imap_get_uncached_messages(IMAPSession *session,
2201 MsgNumberList *numlist)
2203 GSList *result = NULL;
2205 uncached_data *data = g_new0(uncached_data, 1);
2210 data->total = g_slist_length(numlist);
2211 debug_print("messages list : %i\n", data->total);
2213 while (cur != NULL) {
2214 GSList * partial_result;
2222 while (count < MAX_MSG_NUM) {
2227 if (newlist == NULL)
2228 llast = newlist = g_slist_append(newlist, p);
2230 llast = g_slist_append(llast, p);
2231 llast = llast->next;
2241 data->session = session;
2243 data->numlist = newlist;
2246 if (prefs_common.work_offline &&
2247 !inc_offline_should_override(
2248 _("Claws Mail needs network access in order "
2249 "to access the IMAP server."))) {
2255 (GSList *)imap_get_uncached_messages_thread(data);
2257 statusbar_progress_all(data->cur,data->total, 1);
2259 g_slist_free(newlist);
2261 result = g_slist_concat(result, partial_result);
2265 statusbar_progress_all(0,0,0);
2266 statusbar_pop_all();
2271 static void imap_delete_all_cached_messages(FolderItem *item)
2275 g_return_if_fail(item != NULL);
2276 g_return_if_fail(item->folder != NULL);
2277 g_return_if_fail(FOLDER_CLASS(item->folder) == &imap_class);
2279 debug_print("Deleting all cached messages...\n");
2281 dir = folder_item_get_path(item);
2282 if (is_dir_exist(dir))
2283 remove_all_numbered_files(dir);
2286 debug_print("done.\n");
2289 gchar imap_get_path_separator_for_item(FolderItem *item)
2291 Folder *folder = NULL;
2292 IMAPFolder *imap_folder = NULL;
2293 IMAPSession *session = NULL;
2298 folder = item->folder;
2303 imap_folder = IMAP_FOLDER(folder);
2308 debug_print("getting session...");
2309 session = imap_session_get(FOLDER(folder));
2310 result = imap_get_path_separator(session, imap_folder, item->path);
2315 static gchar imap_refresh_path_separator(IMAPSession *session, IMAPFolder *folder, const gchar *subfolder)
2319 gchar separator = '\0';
2321 g_return_val_if_fail(session != NULL, '/');
2322 r = imap_threaded_list((Folder *)folder, "", subfolder, &lep_list);
2324 if (r != MAILIMAP_NO_ERROR) {
2325 log_warning(_("LIST failed\n"));
2329 if (clist_count(lep_list) > 0) {
2330 clistiter * iter = clist_begin(lep_list);
2331 struct mailimap_mailbox_list * mb;
2332 mb = clist_content(iter);
2334 separator = mb->mb_delimiter;
2335 debug_print("got separator: %c\n", folder->last_seen_separator);
2337 mailimap_list_result_free(lep_list);
2341 static gchar imap_get_path_separator(IMAPSession *session, IMAPFolder *folder, const gchar *path)
2343 gchar separator = '/';
2345 if (folder->last_seen_separator == 0) {
2346 folder->last_seen_separator = imap_refresh_path_separator(session, folder, "");
2349 if (folder->last_seen_separator == 0) {
2350 folder->last_seen_separator = imap_refresh_path_separator(session, folder, "INBOX");
2353 if (folder->last_seen_separator != 0) {
2354 debug_print("using separator: %c\n", folder->last_seen_separator);
2355 return folder->last_seen_separator;
2361 static gchar *imap_get_real_path(IMAPSession *session, IMAPFolder *folder, const gchar *path)
2366 g_return_val_if_fail(folder != NULL, NULL);
2367 g_return_val_if_fail(path != NULL, NULL);
2369 real_path = imap_utf8_to_modified_utf7(path);
2370 separator = imap_get_path_separator(session, folder, path);
2371 imap_path_separator_subst(real_path, separator);
2376 static gint imap_set_message_flags(IMAPSession *session,
2377 MsgNumberList *numlist,
2385 seq_list = imap_get_lep_set_from_numlist(numlist);
2387 for(cur = seq_list ; cur != NULL ; cur = g_slist_next(cur)) {
2388 struct mailimap_set * imapset;
2390 imapset = cur->data;
2392 ok = imap_cmd_store(session, imapset,
2396 imap_lep_set_free(seq_list);
2398 return IMAP_SUCCESS;
2401 typedef struct _select_data {
2402 IMAPSession *session;
2407 guint32 *uid_validity;
2411 static gint imap_select(IMAPSession *session, IMAPFolder *folder,
2413 gint *exists, gint *recent, gint *unseen,
2414 guint32 *uid_validity, gboolean block)
2418 gint exists_, recent_, unseen_;
2419 guint32 uid_validity_;
2421 if (!exists && !recent && !unseen && !uid_validity) {
2422 if (session->mbox && strcmp(session->mbox, path) == 0)
2423 return IMAP_SUCCESS;
2432 uid_validity = &uid_validity_;
2434 g_free(session->mbox);
2435 session->mbox = NULL;
2437 real_path = imap_get_real_path(session, folder, path);
2439 ok = imap_cmd_select(session, real_path,
2440 exists, recent, unseen, uid_validity, block);
2441 if (ok != IMAP_SUCCESS)
2442 log_warning(_("can't select folder: %s\n"), real_path);
2444 session->mbox = g_strdup(path);
2445 session->folder_content_changed = FALSE;
2452 static gint imap_status(IMAPSession *session, IMAPFolder *folder,
2453 const gchar *path, IMAPFolderItem *item,
2455 guint32 *uid_next, guint32 *uid_validity,
2456 gint *unseen, gboolean block)
2460 struct mailimap_mailbox_data_status * data_status;
2465 real_path = imap_get_real_path(session, folder, path);
2479 r = imap_threaded_status(FOLDER(folder), real_path,
2480 &data_status, mask);
2483 if (r != MAILIMAP_NO_ERROR) {
2484 debug_print("status err %d\n", r);
2488 if (data_status->st_info_list == NULL) {
2489 mailimap_mailbox_data_status_free(data_status);
2490 debug_print("status->st_info_list == NULL\n");
2495 for(iter = clist_begin(data_status->st_info_list) ; iter != NULL ;
2496 iter = clist_next(iter)) {
2497 struct mailimap_status_info * info;
2499 info = clist_content(iter);
2500 switch (info->st_att) {
2501 case MAILIMAP_STATUS_ATT_MESSAGES:
2502 * messages = info->st_value;
2503 got_values |= 1 << 0;
2506 case MAILIMAP_STATUS_ATT_UIDNEXT:
2507 * uid_next = info->st_value;
2508 got_values |= 1 << 2;
2511 case MAILIMAP_STATUS_ATT_UIDVALIDITY:
2512 * uid_validity = info->st_value;
2513 got_values |= 1 << 3;
2516 case MAILIMAP_STATUS_ATT_UNSEEN:
2517 * unseen = info->st_value;
2518 got_values |= 1 << 4;
2522 mailimap_mailbox_data_status_free(data_status);
2524 if (got_values != mask) {
2525 debug_print("status: incomplete values received (%d)\n", got_values);
2528 return IMAP_SUCCESS;
2531 static void imap_free_capabilities(IMAPSession *session)
2533 slist_free_strings(session->capability);
2534 g_slist_free(session->capability);
2535 session->capability = NULL;
2538 /* low-level IMAP4rev1 commands */
2540 static gint imap_cmd_login(IMAPSession *session,
2541 const gchar *user, const gchar *pass,
2547 if (!strcmp(type, "LOGIN") && imap_has_capability(session, "LOGINDISABLED")) {
2548 gint ok = IMAP_ERROR;
2549 if (imap_has_capability(session, "STARTTLS")) {
2551 log_warning(_("Server requires TLS to log in.\n"));
2552 ok = imap_cmd_starttls(session);
2553 if (ok != IMAP_SUCCESS) {
2554 log_warning(_("Can't start TLS session.\n"));
2558 imap_free_capabilities(session);
2559 if (imap_get_capabilities(session) != MAILIMAP_NO_ERROR) {
2560 log_warning(_("Can't refresh capabilities.\n"));
2565 log_error(_("Connection to %s failed: "
2566 "server requires TLS, but Claws Mail "
2567 "has been compiled without OpenSSL "
2569 SESSION(session)->server);
2573 log_error(_("Server logins are disabled.\n"));
2578 log_print("IMAP4> Logging %s to %s using %s\n",
2580 SESSION(session)->server,
2582 r = imap_threaded_login(session->folder, user, pass, type);
2583 if (r != MAILIMAP_NO_ERROR) {
2584 log_print("IMAP4< Error logging in to %s\n",
2585 SESSION(session)->server);
2588 log_print("IMAP4< Login to %s successful\n",
2589 SESSION(session)->server);
2595 static gint imap_cmd_noop(IMAPSession *session)
2598 unsigned int exists;
2600 r = imap_threaded_noop(session->folder, &exists);
2601 if (r != MAILIMAP_NO_ERROR) {
2602 debug_print("noop err %d\n", r);
2605 session->exists = exists;
2606 session_set_access_time(SESSION(session));
2608 return IMAP_SUCCESS;
2612 static gint imap_cmd_starttls(IMAPSession *session)
2616 r = imap_threaded_starttls(session->folder,
2617 SESSION(session)->server, SESSION(session)->port);
2618 if (r != MAILIMAP_NO_ERROR) {
2619 debug_print("starttls err %d\n", r);
2622 return IMAP_SUCCESS;
2626 static gint imap_cmd_select(IMAPSession *session, const gchar *folder,
2627 gint *exists, gint *recent, gint *unseen,
2628 guint32 *uid_validity, gboolean block)
2632 r = imap_threaded_select(session->folder, folder,
2633 exists, recent, unseen, uid_validity);
2634 if (r != MAILIMAP_NO_ERROR) {
2635 debug_print("select err %d\n", r);
2638 return IMAP_SUCCESS;
2641 static gint imap_cmd_examine(IMAPSession *session, const gchar *folder,
2642 gint *exists, gint *recent, gint *unseen,
2643 guint32 *uid_validity, gboolean block)
2647 r = imap_threaded_examine(session->folder, folder,
2648 exists, recent, unseen, uid_validity);
2649 if (r != MAILIMAP_NO_ERROR) {
2650 debug_print("examine err %d\n", r);
2654 return IMAP_SUCCESS;
2657 static gint imap_cmd_create(IMAPSession *session, const gchar *folder)
2661 r = imap_threaded_create(session->folder, folder);
2662 if (r != MAILIMAP_NO_ERROR) {
2667 return IMAP_SUCCESS;
2670 static gint imap_cmd_rename(IMAPSession *session, const gchar *old_folder,
2671 const gchar *new_folder)
2675 r = imap_threaded_rename(session->folder, old_folder,
2677 if (r != MAILIMAP_NO_ERROR) {
2682 return IMAP_SUCCESS;
2685 static gint imap_cmd_delete(IMAPSession *session, const gchar *folder)
2690 r = imap_threaded_delete(session->folder, folder);
2691 if (r != MAILIMAP_NO_ERROR) {
2696 return IMAP_SUCCESS;
2699 typedef struct _fetch_data {
2700 IMAPSession *session;
2702 const gchar *filename;
2708 static void *imap_cmd_fetch_thread(void *data)
2710 fetch_data *stuff = (fetch_data *)data;
2711 IMAPSession *session = stuff->session;
2712 guint32 uid = stuff->uid;
2713 const gchar *filename = stuff->filename;
2717 r = imap_threaded_fetch_content(session->folder,
2721 r = imap_threaded_fetch_content(session->folder,
2724 if (r != MAILIMAP_NO_ERROR) {
2725 debug_print("fetch err %d\n", r);
2726 return GINT_TO_POINTER(IMAP_ERROR);
2728 return GINT_TO_POINTER(IMAP_SUCCESS);
2731 static gint imap_cmd_fetch(IMAPSession *session, guint32 uid,
2732 const gchar *filename, gboolean headers,
2735 fetch_data *data = g_new0(fetch_data, 1);
2738 data->session = session;
2740 data->filename = filename;
2741 data->headers = headers;
2744 if (prefs_common.work_offline &&
2745 !inc_offline_should_override(
2746 _("Claws Mail needs network access in order "
2747 "to access the IMAP server."))) {
2751 statusbar_print_all(_("Fetching message..."));
2752 result = GPOINTER_TO_INT(imap_cmd_fetch_thread(data));
2753 statusbar_pop_all();
2759 static gint imap_cmd_append(IMAPSession *session, const gchar *destfolder,
2760 const gchar *file, IMAPFlags flags,
2763 struct mailimap_flag_list * flag_list;
2766 g_return_val_if_fail(file != NULL, IMAP_ERROR);
2768 flag_list = imap_flag_to_lep(flags);
2769 r = imap_threaded_append(session->folder, destfolder,
2770 file, flag_list, (int *)new_uid);
2771 mailimap_flag_list_free(flag_list);
2773 if (r != MAILIMAP_NO_ERROR) {
2774 debug_print("append err %d\n", r);
2777 return IMAP_SUCCESS;
2780 static gint imap_cmd_copy(IMAPSession *session, struct mailimap_set * set,
2781 const gchar *destfolder, GRelation *uid_mapping,
2782 struct mailimap_set **source, struct mailimap_set **dest)
2786 g_return_val_if_fail(session != NULL, IMAP_ERROR);
2787 g_return_val_if_fail(set != NULL, IMAP_ERROR);
2788 g_return_val_if_fail(destfolder != NULL, IMAP_ERROR);
2790 r = imap_threaded_copy(session->folder, set, destfolder, source, dest);
2791 if (r != MAILIMAP_NO_ERROR) {
2796 return IMAP_SUCCESS;
2799 static gint imap_cmd_store(IMAPSession *session, struct mailimap_set * set,
2800 IMAPFlags flags, int do_add)
2803 struct mailimap_flag_list * flag_list;
2804 struct mailimap_store_att_flags * store_att_flags;
2806 flag_list = imap_flag_to_lep(flags);
2810 mailimap_store_att_flags_new_add_flags_silent(flag_list);
2813 mailimap_store_att_flags_new_remove_flags_silent(flag_list);
2815 r = imap_threaded_store(session->folder, set, store_att_flags);
2816 mailimap_store_att_flags_free(store_att_flags);
2817 if (r != MAILIMAP_NO_ERROR) {
2822 return IMAP_SUCCESS;
2825 static gint imap_cmd_expunge(IMAPSession *session)
2829 if (prefs_common.work_offline &&
2830 !inc_offline_should_override(
2831 _("Claws Mail needs network access in order "
2832 "to access the IMAP server."))) {
2836 r = imap_threaded_expunge(session->folder);
2837 if (r != MAILIMAP_NO_ERROR) {
2842 return IMAP_SUCCESS;
2845 static void imap_path_separator_subst(gchar *str, gchar separator)
2848 gboolean in_escape = FALSE;
2850 if (!separator || separator == '/') return;
2852 for (p = str; *p != '\0'; p++) {
2853 if (*p == '/' && !in_escape)
2855 else if (*p == '&' && *(p + 1) != '-' && !in_escape)
2857 else if (*p == '-' && in_escape)
2862 static gchar *imap_modified_utf7_to_utf8(const gchar *mutf7_str)
2864 static iconv_t cd = (iconv_t)-1;
2865 static gboolean iconv_ok = TRUE;
2868 size_t norm_utf7_len;
2870 gchar *to_str, *to_p;
2872 gboolean in_escape = FALSE;
2874 if (!iconv_ok) return g_strdup(mutf7_str);
2876 if (cd == (iconv_t)-1) {
2877 cd = iconv_open(CS_INTERNAL, CS_UTF_7);
2878 if (cd == (iconv_t)-1) {
2879 g_warning("iconv cannot convert UTF-7 to %s\n",
2882 return g_strdup(mutf7_str);
2886 /* modified UTF-7 to normal UTF-7 conversion */
2887 norm_utf7 = g_string_new(NULL);
2889 for (p = mutf7_str; *p != '\0'; p++) {
2890 /* replace: '&' -> '+',
2892 escaped ',' -> '/' */
2893 if (!in_escape && *p == '&') {
2894 if (*(p + 1) != '-') {
2895 g_string_append_c(norm_utf7, '+');
2898 g_string_append_c(norm_utf7, '&');
2901 } else if (in_escape && *p == ',') {
2902 g_string_append_c(norm_utf7, '/');
2903 } else if (in_escape && *p == '-') {
2904 g_string_append_c(norm_utf7, '-');
2907 g_string_append_c(norm_utf7, *p);
2911 norm_utf7_p = norm_utf7->str;
2912 norm_utf7_len = norm_utf7->len;
2913 to_len = strlen(mutf7_str) * 5;
2914 to_p = to_str = g_malloc(to_len + 1);
2916 if (iconv(cd, (ICONV_CONST gchar **)&norm_utf7_p, &norm_utf7_len,
2917 &to_p, &to_len) == -1) {
2918 g_warning(_("iconv cannot convert UTF-7 to %s\n"),
2919 conv_get_locale_charset_str());
2920 g_string_free(norm_utf7, TRUE);
2922 return g_strdup(mutf7_str);
2925 /* second iconv() call for flushing */
2926 iconv(cd, NULL, NULL, &to_p, &to_len);
2927 g_string_free(norm_utf7, TRUE);
2933 static gchar *imap_utf8_to_modified_utf7(const gchar *from)
2935 static iconv_t cd = (iconv_t)-1;
2936 static gboolean iconv_ok = TRUE;
2937 gchar *norm_utf7, *norm_utf7_p;
2938 size_t from_len, norm_utf7_len;
2940 gchar *from_tmp, *to, *p;
2941 gboolean in_escape = FALSE;
2943 if (!iconv_ok) return g_strdup(from);
2945 if (cd == (iconv_t)-1) {
2946 cd = iconv_open(CS_UTF_7, CS_INTERNAL);
2947 if (cd == (iconv_t)-1) {
2948 g_warning(_("iconv cannot convert %s to UTF-7\n"),
2951 return g_strdup(from);
2955 /* UTF-8 to normal UTF-7 conversion */
2956 Xstrdup_a(from_tmp, from, return g_strdup(from));
2957 from_len = strlen(from);
2958 norm_utf7_len = from_len * 5;
2959 Xalloca(norm_utf7, norm_utf7_len + 1, return g_strdup(from));
2960 norm_utf7_p = norm_utf7;
2962 #define IS_PRINT(ch) (isprint(ch) && IS_ASCII(ch))
2964 while (from_len > 0) {
2965 if (*from_tmp == '+') {
2966 *norm_utf7_p++ = '+';
2967 *norm_utf7_p++ = '-';
2971 } else if (IS_PRINT(*(guchar *)from_tmp)) {
2972 /* printable ascii char */
2973 *norm_utf7_p = *from_tmp;
2979 size_t conv_len = 0;
2981 /* unprintable char: convert to UTF-7 */
2983 while (!IS_PRINT(*(guchar *)p) && conv_len < from_len) {
2984 conv_len += g_utf8_skip[*(guchar *)p];
2985 p += g_utf8_skip[*(guchar *)p];
2988 from_len -= conv_len;
2989 if (iconv(cd, (ICONV_CONST gchar **)&from_tmp,
2991 &norm_utf7_p, &norm_utf7_len) == -1) {
2992 g_warning(_("iconv cannot convert UTF-8 to UTF-7\n"));
2993 return g_strdup(from);
2996 /* second iconv() call for flushing */
2997 iconv(cd, NULL, NULL, &norm_utf7_p, &norm_utf7_len);
3003 *norm_utf7_p = '\0';
3004 to_str = g_string_new(NULL);
3005 for (p = norm_utf7; p < norm_utf7_p; p++) {
3006 /* replace: '&' -> "&-",
3009 BASE64 '/' -> ',' */
3010 if (!in_escape && *p == '&') {
3011 g_string_append(to_str, "&-");
3012 } else if (!in_escape && *p == '+') {
3013 if (*(p + 1) == '-') {
3014 g_string_append_c(to_str, '+');
3017 g_string_append_c(to_str, '&');
3020 } else if (in_escape && *p == '/') {
3021 g_string_append_c(to_str, ',');
3022 } else if (in_escape && *p == '-') {
3023 g_string_append_c(to_str, '-');
3026 g_string_append_c(to_str, *p);
3032 g_string_append_c(to_str, '-');
3036 g_string_free(to_str, FALSE);
3041 static gboolean imap_rename_folder_func(GNode *node, gpointer data)
3043 FolderItem *item = node->data;
3044 gchar **paths = data;
3045 const gchar *oldpath = paths[0];
3046 const gchar *newpath = paths[1];
3048 gchar *new_itempath;
3051 oldpathlen = strlen(oldpath);
3052 if (strncmp(oldpath, item->path, oldpathlen) != 0) {
3053 g_warning("path doesn't match: %s, %s\n", oldpath, item->path);
3057 base = item->path + oldpathlen;
3058 while (*base == G_DIR_SEPARATOR) base++;
3060 new_itempath = g_strdup(newpath);
3062 new_itempath = g_strconcat(newpath, G_DIR_SEPARATOR_S, base,
3065 item->path = new_itempath;
3070 typedef struct _get_list_uid_data {
3072 IMAPSession *session;
3073 IMAPFolderItem *item;
3074 GSList **msgnum_list;
3076 } get_list_uid_data;
3078 static void *get_list_of_uids_thread(void *data)
3080 get_list_uid_data *stuff = (get_list_uid_data *)data;
3081 Folder *folder = stuff->folder;
3082 IMAPFolderItem *item = stuff->item;
3083 GSList **msgnum_list = stuff->msgnum_list;
3084 gint ok, nummsgs = 0, lastuid_old;
3085 IMAPSession *session;
3086 GSList *uidlist, *elem;
3087 clist * lep_uidlist;
3090 session = stuff->session;
3091 if (session == NULL) {
3093 return GINT_TO_POINTER(-1);
3095 /* no session locking here, it's already locked by caller */
3096 ok = imap_select(session, IMAP_FOLDER(folder), item->item.path,
3097 NULL, NULL, NULL, NULL, TRUE);
3098 if (ok != IMAP_SUCCESS) {
3100 return GINT_TO_POINTER(-1);
3105 r = imap_threaded_search(folder, IMAP_SEARCH_TYPE_SIMPLE, NULL,
3108 if (r == MAILIMAP_NO_ERROR) {
3109 GSList * fetchuid_list;
3112 imap_uid_list_from_lep(lep_uidlist);
3113 mailimap_search_result_free(lep_uidlist);
3115 uidlist = g_slist_concat(fetchuid_list, uidlist);
3118 GSList * fetchuid_list;
3119 carray * lep_uidtab;
3121 r = imap_threaded_fetch_uid(folder, item->lastuid + 1,
3123 if (r == MAILIMAP_NO_ERROR) {
3125 imap_uid_list_from_lep_tab(lep_uidtab);
3126 imap_fetch_uid_list_free(lep_uidtab);
3127 uidlist = g_slist_concat(fetchuid_list, uidlist);
3131 lastuid_old = item->lastuid;
3132 *msgnum_list = g_slist_copy(item->uid_list);
3133 nummsgs = g_slist_length(*msgnum_list);
3134 debug_print("Got %d uids from cache\n", g_slist_length(item->uid_list));
3136 for (elem = uidlist; elem != NULL; elem = g_slist_next(elem)) {
3139 msgnum = GPOINTER_TO_INT(elem->data);
3140 if (msgnum > lastuid_old) {
3141 *msgnum_list = g_slist_prepend(*msgnum_list, GINT_TO_POINTER(msgnum));
3142 item->uid_list = g_slist_prepend(item->uid_list, GINT_TO_POINTER(msgnum));
3145 if(msgnum > item->lastuid)
3146 item->lastuid = msgnum;
3149 g_slist_free(uidlist);
3151 return GINT_TO_POINTER(nummsgs);
3154 static gint get_list_of_uids(IMAPSession *session, Folder *folder, IMAPFolderItem *item, GSList **msgnum_list)
3157 get_list_uid_data *data = g_new0(get_list_uid_data, 1);
3159 data->folder = folder;
3161 data->msgnum_list = msgnum_list;
3162 data->session = session;
3163 if (prefs_common.work_offline &&
3164 !inc_offline_should_override(
3165 _("Claws Mail needs network access in order "
3166 "to access the IMAP server."))) {
3171 result = GPOINTER_TO_INT(get_list_of_uids_thread(data));
3177 gint imap_get_num_list(Folder *folder, FolderItem *_item, GSList **msgnum_list, gboolean *old_uids_valid)
3179 IMAPFolderItem *item = (IMAPFolderItem *)_item;
3180 IMAPSession *session;
3181 gint ok, nummsgs = 0, exists;
3182 guint32 uid_next = 0, uid_val = 0;
3183 GSList *uidlist = NULL;
3185 gboolean selected_folder;
3186 debug_print("get_num_list\n");
3188 g_return_val_if_fail(folder != NULL, -1);
3189 g_return_val_if_fail(item != NULL, -1);
3190 g_return_val_if_fail(item->item.path != NULL, -1);
3191 g_return_val_if_fail(FOLDER_CLASS(folder) == &imap_class, -1);
3192 g_return_val_if_fail(folder->account != NULL, -1);
3194 debug_print("getting session...\n");
3195 session = imap_session_get(folder);
3196 g_return_val_if_fail(session != NULL, -1);
3198 if (FOLDER_ITEM(item)->path)
3199 statusbar_print_all(_("Scanning folder %s%c%s ..."),
3200 FOLDER_ITEM(item)->folder->name,
3202 FOLDER_ITEM(item)->path);
3204 statusbar_print_all(_("Scanning folder %s ..."),
3205 FOLDER_ITEM(item)->folder->name);
3207 selected_folder = (session->mbox != NULL) &&
3208 (!strcmp(session->mbox, item->item.path));
3209 if (selected_folder && time(NULL) - item->use_cache < 2) {
3210 ok = imap_cmd_noop(session);
3211 if (ok != IMAP_SUCCESS) {
3212 debug_print("disconnected!\n");
3213 session = imap_reconnect_if_possible(folder, session);
3214 if (session == NULL) {
3215 statusbar_pop_all();
3220 exists = session->exists;
3222 uid_next = item->c_uid_next;
3223 uid_val = item->c_uid_validity;
3224 *old_uids_valid = TRUE;
3226 if (item->use_cache && time(NULL) - item->use_cache < 2) {
3227 exists = item->c_messages;
3228 uid_next = item->c_uid_next;
3229 uid_val = item->c_uid_validity;
3231 debug_print("using cache %d %d %d\n", exists, uid_next, uid_val);
3233 ok = imap_status(session, IMAP_FOLDER(folder), item->item.path, item,
3234 &exists, &uid_next, &uid_val, NULL, FALSE);
3236 item->item.last_num = uid_next - 1;
3238 item->use_cache = (time_t)0;
3239 if (ok != IMAP_SUCCESS) {
3240 statusbar_pop_all();
3244 if(item->item.mtime == uid_val)
3245 *old_uids_valid = TRUE;
3247 *old_uids_valid = FALSE;
3249 debug_print("Freeing imap uid cache (%d != %d)\n",
3250 (int)item->item.mtime, uid_val);
3252 g_slist_free(item->uid_list);
3253 item->uid_list = NULL;
3255 item->item.mtime = uid_val;
3257 imap_delete_all_cached_messages((FolderItem *)item);
3261 /* If old uid_next matches new uid_next we can be sure no message
3262 was added to the folder */
3263 debug_print("uid_next is %d and item->uid_next %d \n",
3264 uid_next, item->uid_next);
3265 if (uid_next == item->uid_next) {
3266 nummsgs = g_slist_length(item->uid_list);
3268 /* If number of messages is still the same we
3269 know our caches message numbers are still valid,
3270 otherwise if the number of messages has decrease
3271 we discard our cache to start a new scan to find
3272 out which numbers have been removed */
3273 if (exists == nummsgs) {
3274 debug_print("exists == nummsgs\n");
3275 *msgnum_list = g_slist_copy(item->uid_list);
3276 statusbar_pop_all();
3279 } else if (exists < nummsgs) {
3280 debug_print("Freeing imap uid cache");
3282 g_slist_free(item->uid_list);
3283 item->uid_list = NULL;
3288 *msgnum_list = NULL;
3289 statusbar_pop_all();
3294 nummsgs = get_list_of_uids(session, folder, item, &uidlist);
3297 statusbar_pop_all();
3302 if (nummsgs != exists) {
3303 /* Cache contains more messages then folder, we have cached
3304 an old UID of a message that was removed and new messages
3305 have been added too, otherwise the uid_next check would
3307 debug_print("Freeing imap uid cache");
3309 g_slist_free(item->uid_list);
3310 item->uid_list = NULL;
3312 g_slist_free(*msgnum_list);
3314 nummsgs = get_list_of_uids(session, folder, item, &uidlist);
3317 *msgnum_list = uidlist;
3319 dir = folder_item_get_path((FolderItem *)item);
3320 debug_print("removing old messages from %s\n", dir);
3321 remove_numbered_files_not_in_list(dir, *msgnum_list);
3324 item->uid_next = uid_next;
3326 debug_print("get_num_list - ok - %i\n", nummsgs);
3327 statusbar_pop_all();
3332 static MsgInfo *imap_parse_msg(const gchar *file, FolderItem *item)
3337 flags.perm_flags = MSG_NEW|MSG_UNREAD;
3338 flags.tmp_flags = 0;
3340 g_return_val_if_fail(item != NULL, NULL);
3341 g_return_val_if_fail(file != NULL, NULL);
3343 if (folder_has_parent_of_type(item, F_QUEUE)) {
3344 MSG_SET_TMP_FLAGS(flags, MSG_QUEUED);
3345 } else if (folder_has_parent_of_type(item, F_DRAFT)) {
3346 MSG_SET_TMP_FLAGS(flags, MSG_DRAFT);
3349 msginfo = procheader_parse_file(file, flags, FALSE, FALSE);
3350 if (!msginfo) return NULL;
3352 msginfo->plaintext_file = g_strdup(file);
3353 msginfo->folder = item;
3358 GSList *imap_get_msginfos(Folder *folder, FolderItem *item,
3359 GSList *msgnum_list)
3361 IMAPSession *session;
3362 MsgInfoList *ret = NULL;
3365 debug_print("get_msginfos\n");
3367 g_return_val_if_fail(folder != NULL, NULL);
3368 g_return_val_if_fail(item != NULL, NULL);
3369 g_return_val_if_fail(msgnum_list != NULL, NULL);
3371 debug_print("getting session...\n");
3372 session = imap_session_get(folder);
3373 g_return_val_if_fail(session != NULL, NULL);
3375 debug_print("IMAP getting msginfos\n");
3376 ok = imap_select(session, IMAP_FOLDER(folder), item->path,
3377 NULL, NULL, NULL, NULL, FALSE);
3378 if (ok != IMAP_SUCCESS) {
3382 if (!(folder_has_parent_of_type(item, F_DRAFT) ||
3383 folder_has_parent_of_type(item, F_QUEUE))) {
3384 ret = g_slist_concat(ret,
3385 imap_get_uncached_messages(session, item,
3388 MsgNumberList *sorted_list, *elem, *llast = NULL;
3389 gint startnum, lastnum;
3391 sorted_list = g_slist_sort(g_slist_copy(msgnum_list), g_int_compare);
3393 startnum = lastnum = GPOINTER_TO_INT(sorted_list->data);
3395 llast = g_slist_last(ret);
3396 for (elem = sorted_list;; elem = g_slist_next(elem)) {
3400 num = GPOINTER_TO_INT(elem->data);
3402 if (num > lastnum + 1 || elem == NULL) {
3404 for (i = startnum; i <= lastnum; ++i) {
3407 file = imap_fetch_msg(folder, item, i);
3409 MsgInfo *msginfo = imap_parse_msg(file, item);
3410 if (msginfo != NULL) {
3411 msginfo->msgnum = i;
3413 llast = ret = g_slist_append(ret, msginfo);
3415 llast = g_slist_append(llast, msginfo);
3416 llast = llast->next;
3421 session_set_access_time(SESSION(session));
3432 g_slist_free(sorted_list);
3438 MsgInfo *imap_get_msginfo(Folder *folder, FolderItem *item, gint uid)
3440 MsgInfo *msginfo = NULL;
3441 MsgInfoList *msginfolist;
3442 MsgNumberList numlist;
3444 numlist.next = NULL;
3445 numlist.data = GINT_TO_POINTER(uid);
3447 msginfolist = imap_get_msginfos(folder, item, &numlist);
3448 if (msginfolist != NULL) {
3449 msginfo = msginfolist->data;
3450 g_slist_free(msginfolist);
3456 gboolean imap_scan_required(Folder *folder, FolderItem *_item)
3458 IMAPSession *session;
3459 IMAPFolderItem *item = (IMAPFolderItem *)_item;
3460 gint ok, exists = 0, unseen = 0;
3461 guint32 uid_next = 0, uid_val = 0;
3462 gboolean selected_folder;
3464 g_return_val_if_fail(folder != NULL, FALSE);
3465 g_return_val_if_fail(item != NULL, FALSE);
3466 g_return_val_if_fail(item->item.folder != NULL, FALSE);
3467 g_return_val_if_fail(FOLDER_CLASS(item->item.folder) == &imap_class, FALSE);
3469 if (item->item.path == NULL)
3472 debug_print("getting session...\n");
3473 session = imap_session_get(folder);
3474 g_return_val_if_fail(session != NULL, FALSE);
3476 selected_folder = (session->mbox != NULL) &&
3477 (!strcmp(session->mbox, item->item.path));
3478 if (selected_folder && time(NULL) - item->use_cache < 2) {
3479 ok = imap_cmd_noop(session);
3480 if (ok != IMAP_SUCCESS) {
3481 debug_print("disconnected!\n");
3482 session = imap_reconnect_if_possible(folder, session);
3483 if (session == NULL)
3487 if (session->folder_content_changed
3488 || session->exists != item->item.total_msgs) {
3493 ok = imap_status(session, IMAP_FOLDER(folder), item->item.path, IMAP_FOLDER_ITEM(item),
3494 &exists, &uid_next, &uid_val, &unseen, FALSE);
3495 if (ok != IMAP_SUCCESS) {
3500 item->use_cache = time(NULL);
3501 item->c_messages = exists;
3502 item->c_uid_next = uid_next;
3503 item->c_uid_validity = uid_val;
3504 item->c_unseen = unseen;
3505 item->item.last_num = uid_next - 1;
3506 debug_print("uidnext %d, item->uid_next %d, exists %d, item->item.total_msgs %d\n",
3507 uid_next, item->uid_next, exists, item->item.total_msgs);
3508 if ((uid_next != item->uid_next) || (exists != item->item.total_msgs)
3509 || unseen != item->item.unread_msgs || uid_val != item->item.mtime) {
3518 void imap_change_flags(Folder *folder, FolderItem *item, MsgInfo *msginfo, MsgPermFlags newflags)
3520 IMAPSession *session;
3521 IMAPFlags flags_set = 0, flags_unset = 0;
3522 gint ok = IMAP_SUCCESS;
3523 MsgNumberList numlist;
3524 hashtable_data *ht_data = NULL;
3526 g_return_if_fail(folder != NULL);
3527 g_return_if_fail(folder->klass == &imap_class);
3528 g_return_if_fail(item != NULL);
3529 g_return_if_fail(item->folder == folder);
3530 g_return_if_fail(msginfo != NULL);
3531 g_return_if_fail(msginfo->folder == item);
3533 if (!MSG_IS_MARKED(msginfo->flags) && (newflags & MSG_MARKED))
3534 flags_set |= IMAP_FLAG_FLAGGED;
3535 if ( MSG_IS_MARKED(msginfo->flags) && !(newflags & MSG_MARKED))
3536 flags_unset |= IMAP_FLAG_FLAGGED;
3538 if (!MSG_IS_UNREAD(msginfo->flags) && (newflags & MSG_UNREAD))
3539 flags_unset |= IMAP_FLAG_SEEN;
3540 if ( MSG_IS_UNREAD(msginfo->flags) && !(newflags & MSG_UNREAD))
3541 flags_set |= IMAP_FLAG_SEEN;
3543 if (!MSG_IS_REPLIED(msginfo->flags) && (newflags & MSG_REPLIED))
3544 flags_set |= IMAP_FLAG_ANSWERED;
3545 if ( MSG_IS_REPLIED(msginfo->flags) && !(newflags & MSG_REPLIED))
3546 flags_unset |= IMAP_FLAG_ANSWERED;
3548 if (!MSG_IS_DELETED(msginfo->flags) && (newflags & MSG_DELETED))
3549 flags_set |= IMAP_FLAG_DELETED;
3550 if ( MSG_IS_DELETED(msginfo->flags) && !(newflags & MSG_DELETED))
3551 flags_unset |= IMAP_FLAG_DELETED;
3553 if (!flags_set && !flags_unset) {
3554 /* the changed flags were not translatable to IMAP-speak.
3555 * like MSG_POSTFILTERED, so just apply. */
3556 msginfo->flags.perm_flags = newflags;
3560 debug_print("getting session...\n");
3561 session = imap_session_get(folder);
3566 if ((ok = imap_select(session, IMAP_FOLDER(folder), msginfo->folder->path,
3567 NULL, NULL, NULL, NULL, FALSE)) != IMAP_SUCCESS) {
3571 numlist.next = NULL;
3572 numlist.data = GINT_TO_POINTER(msginfo->msgnum);
3574 if (IMAP_FOLDER_ITEM(item)->batching) {
3575 /* instead of performing an UID STORE command for each message change,
3576 * as a lot of them can change "together", we just fill in hashtables
3577 * and defer the treatment so that we're able to send only one
3580 debug_print("IMAP batch mode on, deferring flags change\n");
3582 ht_data = g_hash_table_lookup(IMAP_FOLDER_ITEM(item)->flags_set_table,
3583 GINT_TO_POINTER(flags_set));
3584 if (ht_data == NULL) {
3585 ht_data = g_new0(hashtable_data, 1);
3586 ht_data->session = session;
3587 ht_data->item = IMAP_FOLDER_ITEM(item);
3588 g_hash_table_insert(IMAP_FOLDER_ITEM(item)->flags_set_table,
3589 GINT_TO_POINTER(flags_set), ht_data);
3591 if (!g_slist_find(ht_data->msglist, GINT_TO_POINTER(msginfo->msgnum)))
3592 ht_data->msglist = g_slist_prepend(ht_data->msglist, GINT_TO_POINTER(msginfo->msgnum));
3595 ht_data = g_hash_table_lookup(IMAP_FOLDER_ITEM(item)->flags_unset_table,
3596 GINT_TO_POINTER(flags_unset));
3597 if (ht_data == NULL) {
3598 ht_data = g_new0(hashtable_data, 1);
3599 ht_data->session = session;
3600 ht_data->item = IMAP_FOLDER_ITEM(item);
3601 g_hash_table_insert(IMAP_FOLDER_ITEM(item)->flags_unset_table,
3602 GINT_TO_POINTER(flags_unset), ht_data);
3604 if (!g_slist_find(ht_data->msglist, GINT_TO_POINTER(msginfo->msgnum)))
3605 ht_data->msglist = g_slist_prepend(ht_data->msglist,
3606 GINT_TO_POINTER(msginfo->msgnum));
3609 debug_print("IMAP changing flags\n");
3611 ok = imap_set_message_flags(session, &numlist, flags_set, TRUE);
3612 if (ok != IMAP_SUCCESS) {
3619 ok = imap_set_message_flags(session, &numlist, flags_unset, FALSE);
3620 if (ok != IMAP_SUCCESS) {
3626 msginfo->flags.perm_flags = newflags;
3631 static gint imap_remove_msg(Folder *folder, FolderItem *item, gint uid)
3634 IMAPSession *session;
3636 MsgNumberList numlist;
3638 g_return_val_if_fail(folder != NULL, -1);
3639 g_return_val_if_fail(FOLDER_CLASS(folder) == &imap_class, -1);
3640 g_return_val_if_fail(item != NULL, -1);
3642 debug_print("getting session...\n");
3643 session = imap_session_get(folder);
3644 if (!session) return -1;
3646 ok = imap_select(session, IMAP_FOLDER(folder), item->path,
3647 NULL, NULL, NULL, NULL, FALSE);
3648 if (ok != IMAP_SUCCESS) {
3652 numlist.next = NULL;
3653 numlist.data = GINT_TO_POINTER(uid);
3655 ok = imap_set_message_flags
3656 (IMAP_SESSION(REMOTE_FOLDER(folder)->session),
3657 &numlist, IMAP_FLAG_DELETED, TRUE);
3658 if (ok != IMAP_SUCCESS) {
3659 log_warning(_("can't set deleted flags: %d\n"), uid);
3664 if (!session->uidplus) {
3665 ok = imap_cmd_expunge(session);
3669 uidstr = g_strdup_printf("%u", uid);
3670 ok = imap_cmd_expunge(session);
3673 if (ok != IMAP_SUCCESS) {
3674 log_warning(_("can't expunge\n"));
3679 IMAP_FOLDER_ITEM(item)->uid_list = g_slist_remove(
3680 IMAP_FOLDER_ITEM(item)->uid_list, numlist.data);
3681 dir = folder_item_get_path(item);
3682 if (is_dir_exist(dir))
3683 remove_numbered_files(dir, uid, uid);
3686 return IMAP_SUCCESS;
3689 static gint compare_msginfo(gconstpointer a, gconstpointer b)
3691 return ((MsgInfo *)a)->msgnum - ((MsgInfo *)b)->msgnum;
3694 static guint gslist_find_next_num(MsgNumberList **list, guint num)
3698 g_return_val_if_fail(list != NULL, -1);
3700 for (elem = *list; elem != NULL; elem = g_slist_next(elem))
3701 if (GPOINTER_TO_INT(elem->data) >= num)
3704 return elem != NULL ? GPOINTER_TO_INT(elem->data) : (gint)-1;
3708 * NEW and DELETED flags are not syncronized
3709 * - The NEW/RECENT flags in IMAP folders can not really be directly
3710 * modified by Sylpheed
3711 * - The DELETE/DELETED flag in IMAP and Sylpheed don't have the same
3712 * meaning, in IMAP it always removes the messages from the FolderItem
3713 * in Sylpheed it can mean to move the message to trash
3716 typedef struct _get_flags_data {
3719 MsgInfoList *msginfo_list;
3720 GRelation *msgflags;
3721 gboolean full_search;
3725 static /*gint*/ void *imap_get_flags_thread(void *data)
3727 get_flags_data *stuff = (get_flags_data *)data;
3728 Folder *folder = stuff->folder;
3729 FolderItem *item = stuff->item;
3730 MsgInfoList *msginfo_list = stuff->msginfo_list;
3731 GRelation *msgflags = stuff->msgflags;
3732 gboolean full_search = stuff->full_search;
3733 IMAPSession *session;
3734 GSList *sorted_list = NULL;
3735 GSList *unseen = NULL, *answered = NULL, *flagged = NULL, *deleted = NULL;
3736 GSList *p_unseen, *p_answered, *p_flagged, *p_deleted;
3738 GSList *seq_list, *cur;
3739 gboolean reverse_seen = FALSE;
3742 gint exists_cnt, unseen_cnt;
3743 gboolean selected_folder;
3745 if (folder == NULL || item == NULL) {
3747 return GINT_TO_POINTER(-1);
3750 debug_print("getting session...\n");
3751 session = imap_session_get(folder);
3752 if (session == NULL) {
3754 return GINT_TO_POINTER(-1);
3757 selected_folder = (session->mbox != NULL) &&
3758 (!strcmp(session->mbox, item->path));
3760 if (!selected_folder) {
3761 ok = imap_select(session, IMAP_FOLDER(folder), item->path,
3762 &exists_cnt, NULL, &unseen_cnt, NULL, TRUE);
3763 if (ok != IMAP_SUCCESS) {
3766 return GINT_TO_POINTER(-1);
3769 if (unseen_cnt > exists_cnt / 2)
3770 reverse_seen = TRUE;
3773 if (item->unread_msgs > item->total_msgs / 2)
3774 reverse_seen = TRUE;
3777 cmd_buf = g_string_new(NULL);
3779 sorted_list = g_slist_sort(g_slist_copy(msginfo_list), compare_msginfo);
3781 seq_list = imap_get_lep_set_from_msglist(msginfo_list);
3783 struct mailimap_set * set;
3784 set = mailimap_set_new_interval(1, 0);
3785 seq_list = g_slist_append(NULL, set);
3788 for (cur = seq_list; cur != NULL; cur = g_slist_next(cur)) {
3789 struct mailimap_set * imapset;
3790 clist * lep_uidlist;
3793 imapset = cur->data;
3795 r = imap_threaded_search(folder, IMAP_SEARCH_TYPE_SEEN,
3796 full_search ? NULL:imapset, &lep_uidlist);
3799 r = imap_threaded_search(folder,
3800 IMAP_SEARCH_TYPE_UNSEEN,
3801 full_search ? NULL:imapset, &lep_uidlist);
3803 if (r == MAILIMAP_NO_ERROR) {
3806 uidlist = imap_uid_list_from_lep(lep_uidlist);
3807 mailimap_search_result_free(lep_uidlist);
3809 unseen = g_slist_concat(unseen, uidlist);
3812 r = imap_threaded_search(folder, IMAP_SEARCH_TYPE_FLAGGED,
3813 full_search ? NULL:imapset, &lep_uidlist);
3814 if (r == MAILIMAP_NO_ERROR) {
3817 uidlist = imap_uid_list_from_lep(lep_uidlist);
3818 mailimap_search_result_free(lep_uidlist);
3820 flagged = g_slist_concat(flagged, uidlist);
3823 if (item->opened || item->processing_pending || item == folder->inbox) {
3824 r = imap_threaded_search(folder, IMAP_SEARCH_TYPE_ANSWERED,
3825 full_search ? NULL:imapset, &lep_uidlist);
3826 if (r == MAILIMAP_NO_ERROR) {
3829 uidlist = imap_uid_list_from_lep(lep_uidlist);
3830 mailimap_search_result_free(lep_uidlist);
3832 answered = g_slist_concat(answered, uidlist);
3835 r = imap_threaded_search(folder, IMAP_SEARCH_TYPE_DELETED,
3836 full_search ? NULL:imapset, &lep_uidlist);
3837 if (r == MAILIMAP_NO_ERROR) {
3840 uidlist = imap_uid_list_from_lep(lep_uidlist);
3841 mailimap_search_result_free(lep_uidlist);
3843 deleted = g_slist_concat(deleted, uidlist);
3849 p_answered = answered;
3850 p_flagged = flagged;
3851 p_deleted = deleted;
3853 for (elem = sorted_list; elem != NULL; elem = g_slist_next(elem)) {
3858 msginfo = (MsgInfo *) elem->data;
3859 flags = msginfo->flags.perm_flags;
3860 wasnew = (flags & MSG_NEW);
3861 if (item->opened || item->processing_pending || item == folder->inbox) {
3862 flags &= ~((reverse_seen ? 0 : MSG_UNREAD | MSG_NEW) | MSG_REPLIED | MSG_MARKED);
3864 flags &= ~((reverse_seen ? 0 : MSG_UNREAD | MSG_NEW | MSG_MARKED));
3867 flags |= MSG_UNREAD | (wasnew ? MSG_NEW : 0);
3868 if (gslist_find_next_num(&p_unseen, msginfo->msgnum) == msginfo->msgnum) {
3869 if (!reverse_seen) {
3870 flags |= MSG_UNREAD | (wasnew ? MSG_NEW : 0);
3872 flags &= ~(MSG_UNREAD | MSG_NEW);
3876 if (gslist_find_next_num(&p_flagged, msginfo->msgnum) == msginfo->msgnum)
3877 flags |= MSG_MARKED;
3879 flags &= ~MSG_MARKED;
3881 if (item->opened || item->processing_pending || item == folder->inbox) {
3882 if (gslist_find_next_num(&p_answered, msginfo->msgnum) == msginfo->msgnum)
3883 flags |= MSG_REPLIED;
3885 flags &= ~MSG_REPLIED;
3886 if (gslist_find_next_num(&p_deleted, msginfo->msgnum) == msginfo->msgnum)
3887 flags |= MSG_DELETED;
3889 flags &= ~MSG_DELETED;
3891 g_relation_insert(msgflags, msginfo, GINT_TO_POINTER(flags));
3894 imap_lep_set_free(seq_list);
3895 g_slist_free(flagged);
3896 g_slist_free(deleted);
3897 g_slist_free(answered);
3898 g_slist_free(unseen);
3899 g_slist_free(sorted_list);
3900 g_string_free(cmd_buf, TRUE);
3904 return GINT_TO_POINTER(0);
3907 static gint imap_get_flags(Folder *folder, FolderItem *item,
3908 MsgInfoList *msginfo_list, GRelation *msgflags)
3911 get_flags_data *data = g_new0(get_flags_data, 1);
3913 data->folder = folder;
3915 data->msginfo_list = msginfo_list;
3916 data->msgflags = msgflags;
3917 data->full_search = FALSE;
3919 GSList *tmp = NULL, *cur;
3921 if (prefs_common.work_offline &&
3922 !inc_offline_should_override(
3923 _("Claws Mail needs network access in order "
3924 "to access the IMAP server."))) {
3929 tmp = folder_item_get_msg_list(item);
3931 if (g_slist_length(tmp) <= g_slist_length(msginfo_list))
3932 data->full_search = TRUE;
3934 for (cur = tmp; cur; cur = cur->next)
3935 procmsg_msginfo_free((MsgInfo *)cur->data);
3939 result = GPOINTER_TO_INT(imap_get_flags_thread(data));
3946 static gboolean process_flags(gpointer key, gpointer value, gpointer user_data)
3948 gboolean flags_set = GPOINTER_TO_INT(user_data);
3949 gint flags_value = GPOINTER_TO_INT(key);
3950 hashtable_data *data = (hashtable_data *)value;
3951 IMAPFolderItem *_item = data->item;
3952 FolderItem *item = (FolderItem *)_item;
3953 gint ok = IMAP_ERROR;
3954 IMAPSession *session = NULL;
3956 debug_print("getting session...\n");
3957 session = imap_session_get(item->folder);
3959 data->msglist = g_slist_reverse(data->msglist);
3961 debug_print("IMAP %ssetting flags to %d for %d messages\n",
3964 g_slist_length(data->msglist));
3967 ok = imap_select(session, IMAP_FOLDER(item->folder), item->path,
3968 NULL, NULL, NULL, NULL, FALSE);
3970 if (ok == IMAP_SUCCESS) {
3971 imap_set_message_flags(data->session, data->msglist, flags_value, flags_set);
3973 g_warning("can't select mailbox %s\n", item->path);
3977 g_slist_free(data->msglist);
3982 static void process_hashtable(IMAPFolderItem *item)
3984 if (item->flags_set_table) {
3985 g_hash_table_foreach_remove(item->flags_set_table, process_flags, GINT_TO_POINTER(TRUE));
3986 g_hash_table_destroy(item->flags_set_table);
3987 item->flags_set_table = NULL;
3989 if (item->flags_unset_table) {
3990 g_hash_table_foreach_remove(item->flags_unset_table, process_flags, GINT_TO_POINTER(FALSE));
3991 g_hash_table_destroy(item->flags_unset_table);
3992 item->flags_unset_table = NULL;
3996 static void imap_set_batch (Folder *folder, FolderItem *_item, gboolean batch)
3998 IMAPFolderItem *item = (IMAPFolderItem *)_item;
4000 g_return_if_fail(item != NULL);
4002 if (item->batching == batch)
4006 item->batching = TRUE;
4007 debug_print("IMAP switching to batch mode\n");
4008 if (!item->flags_set_table) {
4009 item->flags_set_table = g_hash_table_new(NULL, g_direct_equal);
4011 if (!item->flags_unset_table) {
4012 item->flags_unset_table = g_hash_table_new(NULL, g_direct_equal);
4015 debug_print("IMAP switching away from batch mode\n");
4017 process_hashtable(item);
4018 item->batching = FALSE;
4024 /* data types conversion libetpan <-> claws */
4028 #define ETPAN_IMAP_MB_MARKED 1
4029 #define ETPAN_IMAP_MB_UNMARKED 2
4030 #define ETPAN_IMAP_MB_NOSELECT 4
4031 #define ETPAN_IMAP_MB_NOINFERIORS 8
4033 static int imap_flags_to_flags(struct mailimap_mbx_list_flags * imap_flags)
4039 if (imap_flags->mbf_type == MAILIMAP_MBX_LIST_FLAGS_SFLAG) {
4040 switch (imap_flags->mbf_sflag) {
4041 case MAILIMAP_MBX_LIST_SFLAG_MARKED:
4042 flags |= ETPAN_IMAP_MB_MARKED;
4044 case MAILIMAP_MBX_LIST_SFLAG_NOSELECT:
4045 flags |= ETPAN_IMAP_MB_NOSELECT;
4047 case MAILIMAP_MBX_LIST_SFLAG_UNMARKED:
4048 flags |= ETPAN_IMAP_MB_UNMARKED;
4053 for(cur = clist_begin(imap_flags->mbf_oflags) ; cur != NULL ;
4054 cur = clist_next(cur)) {
4055 struct mailimap_mbx_list_oflag * oflag;
4057 oflag = clist_content(cur);
4059 switch (oflag->of_type) {
4060 case MAILIMAP_MBX_LIST_OFLAG_NOINFERIORS:
4061 flags |= ETPAN_IMAP_MB_NOINFERIORS;
4069 static GSList * imap_list_from_lep(IMAPFolder * folder,
4070 clist * list, const gchar * real_path, gboolean all)
4073 GSList * item_list = NULL, *llast = NULL;
4075 for(iter = clist_begin(list) ; iter != NULL ;
4076 iter = clist_next(iter)) {
4077 struct mailimap_mailbox_list * mb;
4085 FolderItem *new_item;
4087 mb = clist_content(iter);
4093 if (mb->mb_flag != NULL)
4094 flags = imap_flags_to_flags(mb->mb_flag);
4096 delimiter = mb->mb_delimiter;
4099 dup_name = strdup(name);
4100 if (delimiter != '\0')
4101 subst_char(dup_name, delimiter, '/');
4103 base = g_path_get_basename(dup_name);
4104 if (base[0] == '.') {
4110 if (!all && path_cmp(name, real_path) == 0) {
4116 if (!all && dup_name[strlen(dup_name)-1] == '/') {
4117 dup_name[strlen(dup_name)-1] = '\0';
4120 loc_name = imap_modified_utf7_to_utf8(base);
4121 loc_path = imap_modified_utf7_to_utf8(dup_name);
4123 new_item = folder_item_new(FOLDER(folder), loc_name, loc_path);
4124 if ((flags & ETPAN_IMAP_MB_NOINFERIORS) != 0)
4125 new_item->no_sub = TRUE;
4126 if (strcmp(dup_name, "INBOX") != 0 &&
4127 ((flags & ETPAN_IMAP_MB_NOSELECT) != 0))
4128 new_item->no_select = TRUE;
4130 if (item_list == NULL)
4131 llast = item_list = g_slist_append(item_list, new_item);
4133 llast = g_slist_append(llast, new_item);
4134 llast = llast->next;
4136 debug_print("folder '%s' found.\n", loc_path);
4147 static GSList * imap_get_lep_set_from_numlist(MsgNumberList *numlist)
4149 GSList *sorted_list, *cur;
4150 guint first, last, next;
4151 GSList *ret_list = NULL, *llast = NULL;
4153 struct mailimap_set * current_set;
4154 unsigned int item_count;
4156 if (numlist == NULL)
4160 current_set = mailimap_set_new_empty();
4162 sorted_list = g_slist_copy(numlist);
4163 sorted_list = g_slist_sort(sorted_list, g_int_compare);
4165 first = GPOINTER_TO_INT(sorted_list->data);
4168 for (cur = sorted_list; cur != NULL; cur = g_slist_next(cur)) {
4169 if (GPOINTER_TO_INT(cur->data) == 0)
4174 last = GPOINTER_TO_INT(cur->data);
4176 next = GPOINTER_TO_INT(cur->next->data);
4180 if (last + 1 != next || next == 0) {
4182 struct mailimap_set_item * item;
4183 item = mailimap_set_item_new(first, last);
4184 mailimap_set_add(current_set, item);
4189 if (count >= IMAP_SET_MAX_COUNT) {
4190 if (ret_list == NULL)
4191 llast = ret_list = g_slist_append(ret_list,
4194 llast = g_slist_append(llast, current_set);
4195 llast = llast->next;
4197 current_set = mailimap_set_new_empty();
4204 if (clist_count(current_set->set_list) > 0) {
4205 ret_list = g_slist_append(ret_list,
4209 g_slist_free(sorted_list);
4214 static GSList * imap_get_lep_set_from_msglist(MsgInfoList *msglist)
4216 MsgNumberList *numlist = NULL;
4220 for (cur = msglist; cur != NULL; cur = g_slist_next(cur)) {
4221 MsgInfo *msginfo = (MsgInfo *) cur->data;
4223 numlist = g_slist_prepend(numlist, GINT_TO_POINTER(msginfo->msgnum));
4225 numlist = g_slist_reverse(numlist);
4226 seq_list = imap_get_lep_set_from_numlist(numlist);
4227 g_slist_free(numlist);
4232 static GSList * imap_uid_list_from_lep(clist * list)
4239 for(iter = clist_begin(list) ; iter != NULL ;
4240 iter = clist_next(iter)) {
4243 puid = clist_content(iter);
4244 result = g_slist_prepend(result, GINT_TO_POINTER(* puid));
4247 result = g_slist_reverse(result);
4251 static GSList * imap_uid_list_from_lep_tab(carray * list)
4258 for(i = 0 ; i < carray_count(list) ; i ++) {
4261 puid = carray_get(list, i);
4262 result = g_slist_prepend(result, GINT_TO_POINTER(* puid));
4264 result = g_slist_reverse(result);
4268 static MsgInfo *imap_envelope_from_lep(struct imap_fetch_env_info * info,
4271 MsgInfo *msginfo = NULL;
4274 MsgFlags flags = {0, 0};
4276 if (info->headers == NULL)
4279 MSG_SET_TMP_FLAGS(flags, MSG_IMAP);
4280 if (folder_has_parent_of_type(item, F_QUEUE)) {
4281 MSG_SET_TMP_FLAGS(flags, MSG_QUEUED);
4282 } else if (folder_has_parent_of_type(item, F_DRAFT)) {
4283 MSG_SET_TMP_FLAGS(flags, MSG_DRAFT);
4285 flags.perm_flags = info->flags;
4289 msginfo = procheader_parse_str(info->headers, flags, FALSE, FALSE);
4292 msginfo->msgnum = uid;
4293 msginfo->size = size;
4299 static void imap_lep_set_free(GSList *seq_list)
4303 for(cur = seq_list ; cur != NULL ; cur = g_slist_next(cur)) {
4304 struct mailimap_set * imapset;
4306 imapset = cur->data;
4307 mailimap_set_free(imapset);
4309 g_slist_free(seq_list);
4312 static struct mailimap_flag_list * imap_flag_to_lep(IMAPFlags flags)
4314 struct mailimap_flag_list * flag_list;
4316 flag_list = mailimap_flag_list_new_empty();
4318 if (IMAP_IS_SEEN(flags))
4319 mailimap_flag_list_add(flag_list,
4320 mailimap_flag_new_seen());
4321 if (IMAP_IS_ANSWERED(flags))
4322 mailimap_flag_list_add(flag_list,
4323 mailimap_flag_new_answered());
4324 if (IMAP_IS_FLAGGED(flags))
4325 mailimap_flag_list_add(flag_list,
4326 mailimap_flag_new_flagged());
4327 if (IMAP_IS_DELETED(flags))
4328 mailimap_flag_list_add(flag_list,
4329 mailimap_flag_new_deleted());
4330 if (IMAP_IS_DRAFT(flags))
4331 mailimap_flag_list_add(flag_list,
4332 mailimap_flag_new_draft());
4337 guint imap_folder_get_refcnt(Folder *folder)
4339 return ((IMAPFolder *)folder)->refcnt;
4342 void imap_folder_ref(Folder *folder)
4344 ((IMAPFolder *)folder)->refcnt++;
4347 void imap_disconnect_all(void)
4350 for (list = account_get_list(); list != NULL; list = list->next) {
4351 PrefsAccount *account = list->data;
4352 if (account->protocol == A_IMAP4) {
4353 RemoteFolder *folder = (RemoteFolder *)account->folder;
4354 if (folder && folder->session) {
4355 IMAPSession *session = (IMAPSession *)folder->session;
4356 imap_threaded_disconnect(FOLDER(folder));
4357 SESSION(session)->state = SESSION_DISCONNECTED;
4358 session_destroy(SESSION(session));
4359 folder->session = NULL;
4365 void imap_folder_unref(Folder *folder)
4367 if (((IMAPFolder *)folder)->refcnt > 0)
4368 ((IMAPFolder *)folder)->refcnt--;
4371 #else /* HAVE_LIBETPAN */
4373 static FolderClass imap_class;
4375 static XMLTag *imap_item_get_xml(Folder *folder, FolderItem *item);
4376 static void imap_item_set_xml(Folder *folder, FolderItem *item, XMLTag *tag);
4378 static Folder *imap_folder_new (const gchar *name,
4381 static gboolean missing_imap_warning = TRUE;
4382 if (missing_imap_warning) {
4383 missing_imap_warning = FALSE;
4385 _("You have one or more IMAP accounts "
4386 "defined. However this version of "
4387 "Claws Mail has been built without "
4388 "IMAP support; your IMAP account(s) are "
4390 "You probably need to "
4391 "install libetpan and recompile "
4396 static gint imap_create_tree (Folder *folder)
4400 static FolderItem *imap_create_folder (Folder *folder,
4406 static gint imap_rename_folder (Folder *folder,
4413 gchar imap_get_path_separator_for_item(FolderItem *item)
4418 FolderClass *imap_get_class(void)
4420 if (imap_class.idstr == NULL) {
4421 imap_class.type = F_IMAP;
4422 imap_class.idstr = "imap";
4423 imap_class.uistr = "IMAP4";
4425 imap_class.new_folder = imap_folder_new;
4426 imap_class.create_tree = imap_create_tree;
4427 imap_class.create_folder = imap_create_folder;
4428 imap_class.rename_folder = imap_rename_folder;
4430 imap_class.set_xml = folder_set_xml;
4431 imap_class.get_xml = folder_get_xml;
4432 imap_class.item_set_xml = imap_item_set_xml;
4433 imap_class.item_get_xml = imap_item_get_xml;
4434 /* nothing implemented */
4440 void imap_disconnect_all(void)
4446 void imap_synchronise(FolderItem *item)
4448 imap_gtk_synchronise(item);
4451 static void imap_item_set_xml(Folder *folder, FolderItem *item, XMLTag *tag)
4453 #ifdef HAVE_LIBETPAN
4456 folder_item_set_xml(folder, item, tag);
4458 #ifdef HAVE_LIBETPAN
4459 for (cur = tag->attr; cur != NULL; cur = g_list_next(cur)) {
4460 XMLAttr *attr = (XMLAttr *) cur->data;
4462 if (!attr || !attr->name || !attr->value) continue;
4463 if (!strcmp(attr->name, "uidnext"))
4464 IMAP_FOLDER_ITEM(item)->uid_next = atoi(attr->value);
4469 static XMLTag *imap_item_get_xml(Folder *folder, FolderItem *item)
4473 tag = folder_item_get_xml(folder, item);
4475 #ifdef HAVE_LIBETPAN
4476 xml_tag_add_attr(tag, xml_attr_new_int("uidnext",
4477 IMAP_FOLDER_ITEM(item)->uid_next));