2 * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2007 Hiroyuki Yamamoto and the Claws Mail team
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
27 #include <glib/gi18n.h>
56 #include "procheader.h"
57 #include "prefs_account.h"
62 #include "prefs_common.h"
63 #include "inputdialog.h"
65 #include "remotefolder.h"
66 #include "alertpanel.h"
68 #include "statusbar.h"
70 #include "imap-thread.h"
73 typedef struct _IMAPFolder IMAPFolder;
74 typedef struct _IMAPSession IMAPSession;
75 typedef struct _IMAPNameSpace IMAPNameSpace;
76 typedef struct _IMAPFolderItem IMAPFolderItem;
78 #include "prefs_account.h"
80 #define IMAP_FOLDER(obj) ((IMAPFolder *)obj)
81 #define IMAP_FOLDER_ITEM(obj) ((IMAPFolderItem *)obj)
82 #define IMAP_SESSION(obj) ((IMAPSession *)obj)
88 /* list of IMAPNameSpace */
92 gchar last_seen_separator;
100 gboolean authenticated;
109 gboolean folder_content_changed;
115 struct _IMAPNameSpace
121 #define IMAP_SUCCESS 0
122 #define IMAP_SOCKET 2
123 #define IMAP_AUTHFAIL 3
124 #define IMAP_PROTOCOL 4
125 #define IMAP_SYNTAX 5
129 #define IMAPBUFSIZE 8192
133 IMAP_FLAG_SEEN = 1 << 0,
134 IMAP_FLAG_ANSWERED = 1 << 1,
135 IMAP_FLAG_FLAGGED = 1 << 2,
136 IMAP_FLAG_DELETED = 1 << 3,
137 IMAP_FLAG_DRAFT = 1 << 4
140 #define IMAP_IS_SEEN(flags) ((flags & IMAP_FLAG_SEEN) != 0)
141 #define IMAP_IS_ANSWERED(flags) ((flags & IMAP_FLAG_ANSWERED) != 0)
142 #define IMAP_IS_FLAGGED(flags) ((flags & IMAP_FLAG_FLAGGED) != 0)
143 #define IMAP_IS_DELETED(flags) ((flags & IMAP_FLAG_DELETED) != 0)
144 #define IMAP_IS_DRAFT(flags) ((flags & IMAP_FLAG_DRAFT) != 0)
147 #define IMAP4_PORT 143
149 #define IMAPS_PORT 993
152 #define IMAP_CMD_LIMIT 1000
154 struct _IMAPFolderItem
166 guint32 c_uid_validity;
169 GHashTable *flags_set_table;
170 GHashTable *flags_unset_table;
173 static XMLTag *imap_item_get_xml(Folder *folder, FolderItem *item);
174 static void imap_item_set_xml(Folder *folder, FolderItem *item, XMLTag *tag);
176 static void imap_folder_init (Folder *folder,
180 static Folder *imap_folder_new (const gchar *name,
182 static void imap_folder_destroy (Folder *folder);
184 static IMAPSession *imap_session_new (Folder *folder,
185 const PrefsAccount *account);
186 static void imap_session_authenticate(IMAPSession *session,
187 const PrefsAccount *account);
188 static void imap_session_destroy (Session *session);
190 static gchar *imap_fetch_msg (Folder *folder,
193 static gchar *imap_fetch_msg_full (Folder *folder,
198 static gint imap_add_msg (Folder *folder,
202 static gint imap_add_msgs (Folder *folder,
205 GRelation *relation);
207 static gint imap_copy_msg (Folder *folder,
210 static gint imap_copy_msgs (Folder *folder,
212 MsgInfoList *msglist,
213 GRelation *relation);
215 static gint imap_remove_msg (Folder *folder,
218 static gint imap_remove_msgs (Folder *folder,
220 MsgInfoList *msglist,
221 GRelation *relation);
222 static gint imap_remove_all_msg (Folder *folder,
225 static gboolean imap_is_msg_changed (Folder *folder,
229 static gint imap_close (Folder *folder,
232 static gint imap_scan_tree (Folder *folder);
234 static gint imap_create_tree (Folder *folder);
236 static FolderItem *imap_create_folder (Folder *folder,
239 static gint imap_rename_folder (Folder *folder,
242 static gint imap_remove_folder (Folder *folder,
245 static FolderItem *imap_folder_item_new (Folder *folder);
246 static void imap_folder_item_destroy (Folder *folder,
249 static IMAPSession *imap_session_get (Folder *folder);
251 static gint imap_auth (IMAPSession *session,
256 static gint imap_scan_tree_recursive (IMAPSession *session,
259 static void imap_create_missing_folders (Folder *folder);
260 static FolderItem *imap_create_special_folder
262 SpecialFolderItemType stype,
265 static gint imap_do_copy_msgs (Folder *folder,
267 MsgInfoList *msglist,
268 GRelation *relation);
270 static void imap_delete_all_cached_messages (FolderItem *item);
271 static void imap_set_batch (Folder *folder,
274 static gint imap_set_message_flags (IMAPSession *session,
275 MsgNumberList *numlist,
278 static gint imap_select (IMAPSession *session,
284 guint32 *uid_validity,
286 static gint imap_status (IMAPSession *session,
289 IMAPFolderItem *item,
292 guint32 *uid_validity,
296 static gchar imap_get_path_separator (IMAPSession *session,
299 static gchar *imap_get_real_path (IMAPSession *session,
302 static void imap_synchronise (FolderItem *item);
304 static void imap_free_capabilities (IMAPSession *session);
306 /* low-level IMAP4rev1 commands */
307 static gint imap_cmd_login (IMAPSession *session,
311 static gint imap_cmd_noop (IMAPSession *session);
313 static gint imap_cmd_starttls (IMAPSession *session);
315 static gint imap_cmd_select (IMAPSession *session,
320 guint32 *uid_validity,
322 static gint imap_cmd_examine (IMAPSession *session,
327 guint32 *uid_validity,
329 static gint imap_cmd_create (IMAPSession *sock,
330 const gchar *folder);
331 static gint imap_cmd_rename (IMAPSession *sock,
332 const gchar *oldfolder,
333 const gchar *newfolder);
334 static gint imap_cmd_delete (IMAPSession *session,
335 const gchar *folder);
336 static gint imap_cmd_fetch (IMAPSession *sock,
338 const gchar *filename,
341 static gint imap_cmd_append (IMAPSession *session,
342 const gchar *destfolder,
346 static gint imap_cmd_copy (IMAPSession *session,
347 struct mailimap_set * set,
348 const gchar *destfolder,
349 GRelation *uid_mapping,
350 struct mailimap_set ** source,
351 struct mailimap_set ** dest);
352 static gint imap_cmd_store (IMAPSession *session,
353 struct mailimap_set * set,
356 static gint imap_cmd_expunge (IMAPSession *session);
358 static void imap_path_separator_subst (gchar *str,
361 static gchar *imap_utf8_to_modified_utf7 (const gchar *from);
362 static gchar *imap_modified_utf7_to_utf8 (const gchar *mutf7_str);
364 static gboolean imap_rename_folder_func (GNode *node,
366 static gint imap_get_num_list (Folder *folder,
369 gboolean *old_uids_valid);
370 static GSList *imap_get_msginfos (Folder *folder,
372 GSList *msgnum_list);
373 static MsgInfo *imap_get_msginfo (Folder *folder,
376 static gboolean imap_scan_required (Folder *folder,
378 static void imap_change_flags (Folder *folder,
381 MsgPermFlags newflags);
382 static gint imap_get_flags (Folder *folder,
384 MsgInfoList *msglist,
385 GRelation *msgflags);
386 static gchar *imap_folder_get_path (Folder *folder);
387 static gchar *imap_item_get_path (Folder *folder,
389 static MsgInfo *imap_parse_msg(const gchar *file, FolderItem *item);
392 /* data types conversion libetpan <-> claws */
393 static GSList * imap_list_from_lep(IMAPFolder * folder,
394 clist * list, const gchar * real_path, gboolean all);
395 static GSList * imap_get_lep_set_from_numlist(MsgNumberList *numlist);
396 static GSList * imap_get_lep_set_from_msglist(MsgInfoList *msglist);
397 static GSList * imap_uid_list_from_lep(clist * list);
398 static GSList * imap_uid_list_from_lep_tab(carray * list);
399 static MsgInfo *imap_envelope_from_lep(struct imap_fetch_env_info * info,
401 static void imap_lep_set_free(GSList *seq_list);
402 static struct mailimap_flag_list * imap_flag_to_lep(IMAPFlags flags);
404 typedef struct _hashtable_data {
405 IMAPSession *session;
407 IMAPFolderItem *item;
410 static FolderClass imap_class;
412 typedef struct _thread_data {
422 FolderClass *imap_get_class(void)
424 if (imap_class.idstr == NULL) {
425 imap_class.type = F_IMAP;
426 imap_class.idstr = "imap";
427 imap_class.uistr = "IMAP4";
429 /* Folder functions */
430 imap_class.new_folder = imap_folder_new;
431 imap_class.destroy_folder = imap_folder_destroy;
432 imap_class.scan_tree = imap_scan_tree;
433 imap_class.create_tree = imap_create_tree;
435 /* FolderItem functions */
436 imap_class.item_new = imap_folder_item_new;
437 imap_class.item_destroy = imap_folder_item_destroy;
438 imap_class.item_get_path = imap_item_get_path;
439 imap_class.create_folder = imap_create_folder;
440 imap_class.rename_folder = imap_rename_folder;
441 imap_class.remove_folder = imap_remove_folder;
442 imap_class.close = imap_close;
443 imap_class.get_num_list = imap_get_num_list;
444 imap_class.scan_required = imap_scan_required;
445 imap_class.set_xml = folder_set_xml;
446 imap_class.get_xml = folder_get_xml;
447 imap_class.item_set_xml = imap_item_set_xml;
448 imap_class.item_get_xml = imap_item_get_xml;
450 /* Message functions */
451 imap_class.get_msginfo = imap_get_msginfo;
452 imap_class.get_msginfos = imap_get_msginfos;
453 imap_class.fetch_msg = imap_fetch_msg;
454 imap_class.fetch_msg_full = imap_fetch_msg_full;
455 imap_class.add_msg = imap_add_msg;
456 imap_class.add_msgs = imap_add_msgs;
457 imap_class.copy_msg = imap_copy_msg;
458 imap_class.copy_msgs = imap_copy_msgs;
459 imap_class.remove_msg = imap_remove_msg;
460 imap_class.remove_msgs = imap_remove_msgs;
461 imap_class.remove_all_msg = imap_remove_all_msg;
462 imap_class.is_msg_changed = imap_is_msg_changed;
463 imap_class.change_flags = imap_change_flags;
464 imap_class.get_flags = imap_get_flags;
465 imap_class.set_batch = imap_set_batch;
466 imap_class.synchronise = imap_synchronise;
468 pthread_mutex_init(&imap_mutex, NULL);
475 static Folder *imap_folder_new(const gchar *name, const gchar *path)
479 folder = (Folder *)g_new0(IMAPFolder, 1);
480 folder->klass = &imap_class;
481 imap_folder_init(folder, name, path);
486 static void imap_folder_destroy(Folder *folder)
488 while (imap_folder_get_refcnt(folder) > 0)
489 gtk_main_iteration();
491 folder_remote_folder_destroy(REMOTE_FOLDER(folder));
495 static void imap_folder_init(Folder *folder, const gchar *name,
498 folder_remote_folder_init((Folder *)folder, name, path);
501 static FolderItem *imap_folder_item_new(Folder *folder)
503 IMAPFolderItem *item;
505 item = g_new0(IMAPFolderItem, 1);
508 item->uid_list = NULL;
510 return (FolderItem *)item;
513 static void imap_folder_item_destroy(Folder *folder, FolderItem *_item)
515 IMAPFolderItem *item = (IMAPFolderItem *)_item;
517 g_return_if_fail(item != NULL);
518 g_slist_free(item->uid_list);
523 static gboolean imap_reset_uid_lists_func(GNode *node, gpointer data)
525 IMAPFolderItem *item = (IMAPFolderItem *)node->data;
528 g_slist_free(item->uid_list);
529 item->uid_list = NULL;
534 static void imap_reset_uid_lists(Folder *folder)
536 if(folder->node == NULL)
539 /* Destroy all uid lists and rest last uid */
540 g_node_traverse(folder->node, G_IN_ORDER, G_TRAVERSE_ALL, -1, imap_reset_uid_lists_func, NULL);
543 int imap_get_capabilities(IMAPSession *session)
545 struct mailimap_capability_data *capabilities = NULL;
549 if (session->capability != NULL)
550 return MAILIMAP_NO_ERROR;
552 capabilities = imap_threaded_capability(session->folder, &result);
554 if (result != MAILIMAP_NO_ERROR) {
555 return MAILIMAP_ERROR_CAPABILITY;
558 if (capabilities == NULL) {
559 return MAILIMAP_NO_ERROR;
562 for(cur = clist_begin(capabilities->cap_list) ; cur != NULL ;
563 cur = clist_next(cur)) {
564 struct mailimap_capability * cap =
566 if (!cap || cap->cap_data.cap_name == NULL)
568 session->capability = g_slist_append
569 (session->capability,
570 g_strdup(cap->cap_data.cap_name));
571 debug_print("got capa %s\n", cap->cap_data.cap_name);
573 mailimap_capability_data_free(capabilities);
574 return MAILIMAP_NO_ERROR;
577 gboolean imap_has_capability(IMAPSession *session, const gchar *cap)
580 for (cur = session->capability; cur; cur = cur->next) {
581 if (!g_ascii_strcasecmp(cur->data, cap))
587 static gint imap_auth(IMAPSession *session, const gchar *user, const gchar *pass,
590 gint ok = IMAP_ERROR;
591 static time_t last_login_err = 0;
592 gchar *ext_info = "";
594 if (imap_get_capabilities(session) != MAILIMAP_NO_ERROR)
599 ok = imap_cmd_login(session, user, pass, "ANONYMOUS");
601 case IMAP_AUTH_CRAM_MD5:
602 ok = imap_cmd_login(session, user, pass, "CRAM-MD5");
604 case IMAP_AUTH_LOGIN:
605 ok = imap_cmd_login(session, user, pass, "LOGIN");
607 case IMAP_AUTH_GSSAPI:
608 ok = imap_cmd_login(session, user, pass, "GSSAPI");
611 debug_print("capabilities:\n"
616 imap_has_capability(session, "ANONYMOUS"),
617 imap_has_capability(session, "CRAM-MD5"),
618 imap_has_capability(session, "LOGIN"),
619 imap_has_capability(session, "GSSAPI"));
620 if (imap_has_capability(session, "CRAM-MD5"))
621 ok = imap_cmd_login(session, user, pass, "CRAM-MD5");
622 if (ok == IMAP_ERROR && imap_has_capability(session, "GSSAPI"))
623 ok = imap_cmd_login(session, user, pass, "GSSAPI");
624 if (ok == IMAP_ERROR) /* we always try LOGIN before giving up */
625 ok = imap_cmd_login(session, user, pass, "LOGIN");
628 if (ok == IMAP_SUCCESS)
629 session->authenticated = TRUE;
631 if (type == IMAP_AUTH_CRAM_MD5) {
632 ext_info = _("\n\nCRAM-MD5 logins only work if libetpan has been "
633 "compiled with SASL support and the "
634 "CRAM-MD5 SASL plugin is installed.");
637 if (time(NULL) - last_login_err > 10) {
638 if (!prefs_common.no_recv_err_panel) {
639 alertpanel_error(_("Connection to %s failed: "
641 SESSION(session)->server, ext_info);
643 log_error(_("Connection to %s failed: "
644 "login refused.%s\n"),
645 SESSION(session)->server, ext_info);
648 last_login_err = time(NULL);
653 static IMAPSession *imap_reconnect_if_possible(Folder *folder, IMAPSession *session)
655 RemoteFolder *rfolder = REMOTE_FOLDER(folder);
656 /* Check if this is the first try to establish a
657 connection, if yes we don't try to reconnect */
658 debug_print("reconnecting\n");
659 if (rfolder->session == NULL) {
660 log_warning(_("Connecting to %s failed"),
661 folder->account->recv_server);
662 session_destroy(SESSION(session));
665 log_warning(_("IMAP4 connection to %s has been"
666 " disconnected. Reconnecting...\n"),
667 folder->account->recv_server);
668 statusbar_print_all(_("IMAP4 connection to %s has been"
669 " disconnected. Reconnecting...\n"),
670 folder->account->recv_server);
671 SESSION(session)->state = SESSION_DISCONNECTED;
672 session_destroy(SESSION(session));
673 /* Clear folders session to make imap_session_get create
674 a new session, because of rfolder->session == NULL
675 it will not try to reconnect again and so avoid an
677 rfolder->session = NULL;
678 debug_print("getting session...\n");
679 session = imap_session_get(folder);
680 rfolder->session = SESSION(session);
686 #define lock_session() {\
688 debug_print("locking session %p (%d)\n", session, session->busy); \
690 debug_print(" SESSION WAS LOCKED !! \n"); \
691 session->busy = TRUE;\
693 debug_print("can't lock null session\n"); \
697 #define unlock_session() {\
699 debug_print("unlocking session %p\n", session); \
700 session->busy = FALSE;\
702 debug_print("can't unlock null session\n"); \
706 static IMAPSession *imap_session_get(Folder *folder)
708 RemoteFolder *rfolder = REMOTE_FOLDER(folder);
709 IMAPSession *session = NULL;
711 g_return_val_if_fail(folder != NULL, NULL);
712 g_return_val_if_fail(FOLDER_CLASS(folder) == &imap_class, NULL);
713 g_return_val_if_fail(folder->account != NULL, NULL);
715 if (prefs_common.work_offline &&
716 !inc_offline_should_override(
717 _("Claws Mail needs network access in order "
718 "to access the IMAP server."))) {
722 /* Make sure we have a session */
723 if (rfolder->session != NULL) {
724 session = IMAP_SESSION(rfolder->session);
726 imap_reset_uid_lists(folder);
727 if (time(NULL) - rfolder->last_failure <= 2)
729 session = imap_session_new(folder, folder->account);
731 if(session == NULL) {
732 rfolder->last_failure = time(NULL);
736 /* Make sure session is authenticated */
737 if (!IMAP_SESSION(session)->authenticated)
738 imap_session_authenticate(IMAP_SESSION(session), folder->account);
740 if (!IMAP_SESSION(session)->authenticated) {
741 imap_threaded_disconnect(session->folder);
742 SESSION(session)->state = SESSION_DISCONNECTED;
743 session_destroy(SESSION(session));
744 rfolder->session = NULL;
745 rfolder->last_failure = time(NULL);
751 /* I think the point of this code is to avoid sending a
752 * keepalive if we've used the session recently and therefore
753 * think it's still alive. Unfortunately, most of the code
754 * does not yet check for errors on the socket, and so if the
755 * connection drops we don't notice until the timeout expires.
756 * A better solution than sending a NOOP every time would be
757 * for every command to be prepared to retry until it is
758 * successfully sent. -- mbp */
759 if (time(NULL) - SESSION(session)->last_access_time > SESSION_TIMEOUT_INTERVAL) {
760 /* verify that the session is still alive */
761 if (imap_cmd_noop(session) != IMAP_SUCCESS) {
762 debug_print("disconnected!\n");
763 session = imap_reconnect_if_possible(folder, session);
767 rfolder->session = SESSION(session);
769 return IMAP_SESSION(session);
772 static IMAPSession *imap_session_new(Folder * folder,
773 const PrefsAccount *account)
775 IMAPSession *session;
778 int authenticated = FALSE;
781 /* FIXME: IMAP over SSL only... */
784 port = account->set_imapport ? account->imapport
785 : account->ssl_imap == SSL_TUNNEL ? IMAPS_PORT : IMAP4_PORT;
786 ssl_type = account->ssl_imap;
788 if (account->ssl_imap != SSL_NONE) {
789 if (alertpanel_full(_("Insecure connection"),
790 _("This connection is configured to be secured "
791 "using SSL, but SSL is not available in this "
792 "build of Claws Mail. \n\n"
793 "Do you want to continue connecting to this "
794 "server? The communication would not be "
796 GTK_STOCK_CANCEL, _("Con_tinue connecting"),
797 NULL, FALSE, NULL, ALERT_WARNING,
798 G_ALERTDEFAULT) != G_ALERTALTERNATE)
801 port = account->set_imapport ? account->imapport
806 statusbar_print_all(_("Connecting to IMAP4 server: %s..."), folder->account->recv_server);
807 if (account->set_tunnelcmd) {
808 r = imap_threaded_connect_cmd(folder,
810 account->recv_server,
815 if (ssl_type == SSL_TUNNEL) {
816 r = imap_threaded_connect_ssl(folder,
817 account->recv_server,
823 r = imap_threaded_connect(folder,
824 account->recv_server,
830 if (r == MAILIMAP_NO_ERROR_AUTHENTICATED) {
831 authenticated = TRUE;
833 else if (r == MAILIMAP_NO_ERROR_NON_AUTHENTICATED) {
834 authenticated = FALSE;
837 #if (LIBETPAN_VERSION_MAJOR > 0 || LIBETPAN_VERSION_MINOR > 48)
839 if (r == MAILIMAP_ERROR_SSL)
840 log_error(_("SSL handshake failed\n"));
843 if(!prefs_common.no_recv_err_panel) {
844 alertpanel_error_log(_("Can't connect to IMAP4 server: %s:%d"),
845 account->recv_server, port);
847 log_error(_("Can't connect to IMAP4 server: %s:%d\n"),
848 account->recv_server, port);
854 session = g_new0(IMAPSession, 1);
855 session_init(SESSION(session));
856 SESSION(session)->type = SESSION_IMAP;
857 SESSION(session)->server = g_strdup(account->recv_server);
858 SESSION(session)->sock = NULL;
860 SESSION(session)->destroy = imap_session_destroy;
862 session->capability = NULL;
864 session->authenticated = authenticated;
865 session->mbox = NULL;
866 session->cmd_count = 0;
867 session->folder = folder;
868 IMAP_FOLDER(session->folder)->last_seen_separator = 0;
871 if (account->ssl_imap == SSL_STARTTLS) {
874 ok = imap_cmd_starttls(session);
875 if (ok != IMAP_SUCCESS) {
876 log_warning(_("Can't start TLS session.\n"));
877 session_destroy(SESSION(session));
881 imap_free_capabilities(session);
882 session->authenticated = FALSE;
883 session->uidplus = FALSE;
884 session->cmd_count = 1;
887 log_message("IMAP connection is %s-authenticated\n",
888 (session->authenticated) ? "pre" : "un");
893 static void imap_session_authenticate(IMAPSession *session,
894 const PrefsAccount *account)
896 gchar *pass, *acc_pass;
897 gboolean failed = FALSE;
899 g_return_if_fail(account->userid != NULL);
900 acc_pass = account->passwd;
903 if (!pass && account->imap_auth_type != IMAP_AUTH_ANON) {
905 tmp_pass = input_dialog_query_password(account->recv_server, account->userid);
908 Xstrdup_a(pass, tmp_pass, {g_free(tmp_pass); return;});
910 } else if (account->imap_auth_type == IMAP_AUTH_ANON) {
913 statusbar_print_all(_("Connecting to IMAP4 server %s...\n"),
914 account->recv_server);
915 if (imap_auth(session, account->userid, pass, account->imap_auth_type) != IMAP_SUCCESS) {
923 if (prefs_common.no_recv_err_panel) {
924 log_error(_("Couldn't login to IMAP server %s."), account->recv_server);
925 mainwindow_show_error();
927 alertpanel_error_log(_("Couldn't login to IMAP server %s."), account->recv_server);
934 session->authenticated = TRUE;
938 static void imap_session_destroy(Session *session)
940 if (session->state != SESSION_DISCONNECTED)
941 imap_threaded_disconnect(IMAP_SESSION(session)->folder);
943 imap_free_capabilities(IMAP_SESSION(session));
944 g_free(IMAP_SESSION(session)->mbox);
945 sock_close(session->sock);
946 session->sock = NULL;
949 static gchar *imap_fetch_msg(Folder *folder, FolderItem *item, gint uid)
951 return imap_fetch_msg_full(folder, item, uid, TRUE, TRUE);
954 static guint get_size_with_crs(MsgInfo *info)
963 fp = procmsg_open_message(info);
967 while (fgets(buf, sizeof (buf), fp) != NULL) {
969 if (!strstr(buf, "\r") && strstr(buf, "\n"))
977 static gchar *imap_fetch_msg_full(Folder *folder, FolderItem *item, gint uid,
978 gboolean headers, gboolean body)
980 gchar *path, *filename;
981 IMAPSession *session;
984 g_return_val_if_fail(folder != NULL, NULL);
985 g_return_val_if_fail(item != NULL, NULL);
990 path = folder_item_get_path(item);
991 if (!is_dir_exist(path))
993 filename = g_strconcat(path, G_DIR_SEPARATOR_S, itos(uid), NULL);
995 debug_print("trying to fetch cached %s\n", filename);
996 if (is_file_exist(filename)) {
997 /* see whether the local file represents the whole message
998 * or not. As the IMAP server reports size with \r chars,
999 * we have to update the local file (UNIX \n only) size */
1000 MsgInfo *msginfo = imap_parse_msg(filename, item);
1001 MsgInfo *cached = msgcache_get_msg(item->cache,uid);
1002 guint have_size = get_size_with_crs(msginfo);
1005 debug_print("message %d has been already %scached (%d/%d).\n", uid,
1006 have_size >= cached->size ? "fully ":"",
1007 have_size, (int)cached->size);
1009 if (cached && (cached->size <= have_size || !body)) {
1010 procmsg_msginfo_free(cached);
1011 procmsg_msginfo_free(msginfo);
1012 file_strip_crs(filename);
1014 } else if (!cached && time(NULL) - get_file_mtime(filename) < 60) {
1015 debug_print("message not cached and file recent, considering file complete\n");
1016 procmsg_msginfo_free(msginfo);
1017 file_strip_crs(filename);
1020 procmsg_msginfo_free(cached);
1021 procmsg_msginfo_free(msginfo);
1025 debug_print("getting session...\n");
1026 session = imap_session_get(folder);
1033 debug_print("IMAP fetching messages\n");
1034 ok = imap_select(session, IMAP_FOLDER(folder), item->path,
1035 NULL, NULL, NULL, NULL, FALSE);
1036 if (ok != IMAP_SUCCESS) {
1037 g_warning("can't select mailbox %s\n", item->path);
1043 debug_print("getting message %d...\n", uid);
1044 ok = imap_cmd_fetch(session, (guint32)uid, filename, headers, body);
1046 if (ok != IMAP_SUCCESS) {
1047 g_warning("can't fetch message %d\n", uid);
1054 file_strip_crs(filename);
1058 static gint imap_add_msg(Folder *folder, FolderItem *dest,
1059 const gchar *file, MsgFlags *flags)
1063 MsgFileInfo fileinfo;
1065 g_return_val_if_fail(file != NULL, -1);
1067 fileinfo.msginfo = NULL;
1068 fileinfo.file = (gchar *)file;
1069 fileinfo.flags = flags;
1070 file_list.data = &fileinfo;
1071 file_list.next = NULL;
1073 ret = imap_add_msgs(folder, dest, &file_list, NULL);
1077 static gint imap_add_msgs(Folder *folder, FolderItem *dest, GSList *file_list,
1078 GRelation *relation)
1081 IMAPSession *session;
1082 guint32 last_uid = 0;
1084 MsgFileInfo *fileinfo;
1086 gint curnum = 0, total = 0;
1089 g_return_val_if_fail(folder != NULL, -1);
1090 g_return_val_if_fail(dest != NULL, -1);
1091 g_return_val_if_fail(file_list != NULL, -1);
1093 debug_print("getting session...\n");
1094 session = imap_session_get(folder);
1098 destdir = imap_get_real_path(session, IMAP_FOLDER(folder), dest->path);
1100 statusbar_print_all(_("Adding messages..."));
1101 total = g_slist_length(file_list);
1102 for (cur = file_list; cur != NULL; cur = cur->next) {
1103 IMAPFlags iflags = 0;
1104 guint32 new_uid = 0;
1105 gchar *real_file = NULL;
1106 fileinfo = (MsgFileInfo *)cur->data;
1108 statusbar_progress_all(curnum, total, total < 10 ? 1:10);
1111 if (fileinfo->flags) {
1112 if (MSG_IS_MARKED(*fileinfo->flags))
1113 iflags |= IMAP_FLAG_FLAGGED;
1114 if (MSG_IS_REPLIED(*fileinfo->flags))
1115 iflags |= IMAP_FLAG_ANSWERED;
1116 if (!MSG_IS_UNREAD(*fileinfo->flags))
1117 iflags |= IMAP_FLAG_SEEN;
1120 if (real_file == NULL)
1121 real_file = g_strdup(fileinfo->file);
1123 if (folder_has_parent_of_type(dest, F_QUEUE) ||
1124 folder_has_parent_of_type(dest, F_OUTBOX) ||
1125 folder_has_parent_of_type(dest, F_DRAFT) ||
1126 folder_has_parent_of_type(dest, F_TRASH))
1127 iflags |= IMAP_FLAG_SEEN;
1129 ok = imap_cmd_append(session, destdir, real_file, iflags,
1132 if (ok != IMAP_SUCCESS) {
1133 g_warning("can't append message %s\n", real_file);
1137 statusbar_progress_all(0,0,0);
1138 statusbar_pop_all();
1141 debug_print("appended new message as %d\n", new_uid);
1142 /* put the local file in the imapcache, so that we don't
1143 * have to fetch it back later. */
1145 gchar *cache_path = folder_item_get_path(dest);
1146 if (!is_dir_exist(cache_path))
1147 make_dir_hier(cache_path);
1148 if (is_dir_exist(cache_path)) {
1149 gchar *cache_file = g_strconcat(
1150 cache_path, G_DIR_SEPARATOR_S,
1151 itos(new_uid), NULL);
1152 copy_file(real_file, cache_file, TRUE);
1153 debug_print("copied to cache: %s\n", cache_file);
1160 if (relation != NULL)
1161 g_relation_insert(relation, fileinfo->msginfo != NULL ?
1162 (gpointer) fileinfo->msginfo : (gpointer) fileinfo,
1163 GINT_TO_POINTER(dest->last_num + 1));
1165 new_uid = dest->last_num+1;
1167 if (last_uid < new_uid) {
1173 statusbar_progress_all(0,0,0);
1174 statusbar_pop_all();
1176 imap_cmd_expunge(session);
1184 static gint imap_do_copy_msgs(Folder *folder, FolderItem *dest,
1185 MsgInfoList *msglist, GRelation *relation)
1189 GSList *seq_list, *cur;
1191 IMAPSession *session;
1192 gint ok = IMAP_SUCCESS;
1193 GRelation *uid_mapping;
1195 gboolean single = FALSE;
1197 g_return_val_if_fail(folder != NULL, -1);
1198 g_return_val_if_fail(dest != NULL, -1);
1199 g_return_val_if_fail(msglist != NULL, -1);
1201 debug_print("getting session...\n");
1202 session = imap_session_get(folder);
1208 msginfo = (MsgInfo *)msglist->data;
1209 if (msglist->next == NULL)
1211 src = msginfo->folder;
1213 g_warning("the src folder is identical to the dest.\n");
1218 if (src->folder != dest->folder) {
1219 GSList *infolist = NULL, *cur;
1221 for (cur = msglist; cur; cur = cur->next) {
1222 msginfo = (MsgInfo *)cur->data;
1223 MsgFileInfo *fileinfo = g_new0(MsgFileInfo, 1);
1224 fileinfo->file = procmsg_get_message_file(msginfo);
1225 fileinfo->flags = &(msginfo->flags);
1226 infolist = g_slist_prepend(infolist, fileinfo);
1228 infolist = g_slist_reverse(infolist);
1230 res = folder_item_add_msgs(dest, infolist, FALSE);
1231 for (cur = infolist; cur; cur = cur->next) {
1232 MsgFileInfo *info = (MsgFileInfo *)cur->data;
1236 g_slist_free(infolist);
1240 ok = imap_select(session, IMAP_FOLDER(folder), msginfo->folder->path,
1241 NULL, NULL, NULL, NULL, FALSE);
1242 if (ok != IMAP_SUCCESS) {
1247 destdir = imap_get_real_path(session, IMAP_FOLDER(folder), dest->path);
1248 seq_list = imap_get_lep_set_from_msglist(msglist);
1249 uid_mapping = g_relation_new(2);
1250 g_relation_index(uid_mapping, 0, g_direct_hash, g_direct_equal);
1252 statusbar_print_all(_("Copying messages..."));
1253 for (cur = seq_list; cur != NULL; cur = g_slist_next(cur)) {
1254 struct mailimap_set * seq_set;
1255 struct mailimap_set * source = NULL;
1256 struct mailimap_set * dest = NULL;
1257 seq_set = cur->data;
1259 debug_print("Copying messages from %s to %s ...\n",
1260 src->path, destdir);
1262 ok = imap_cmd_copy(session, seq_set, destdir, uid_mapping,
1265 if (ok == IMAP_SUCCESS) {
1266 if (single && relation && source && dest) {
1267 clistiter *l = clist_begin(source->set_list);
1268 struct mailimap_set_item *i = (struct mailimap_set_item *)clist_content(l);
1269 int snum = i->set_first;
1271 l = clist_begin(dest->set_list);
1272 i = (struct mailimap_set_item *)clist_content(l);
1273 dnum = i->set_first;
1274 g_relation_insert(uid_mapping, GINT_TO_POINTER(snum),
1275 GINT_TO_POINTER(dnum));
1281 mailimap_set_free(source);
1283 mailimap_set_free(dest);
1285 if (ok != IMAP_SUCCESS) {
1286 g_relation_destroy(uid_mapping);
1287 imap_lep_set_free(seq_list);
1293 for (cur = msglist; cur != NULL; cur = g_slist_next(cur)) {
1294 MsgInfo *msginfo = (MsgInfo *)cur->data;
1297 tuples = g_relation_select(uid_mapping,
1298 GINT_TO_POINTER(msginfo->msgnum),
1300 if (tuples->len > 0) {
1301 gint num = GPOINTER_TO_INT(g_tuples_index(tuples, 0, 1));
1302 g_relation_insert(relation, msginfo,
1303 GINT_TO_POINTER(num));
1306 debug_print("copied new message as %d\n", num);
1307 /* put the local file in the imapcache, so that we don't
1308 * have to fetch it back later. */
1310 gchar *cache_path = folder_item_get_path(msginfo->folder);
1311 gchar *real_file = g_strconcat(
1312 cache_path, G_DIR_SEPARATOR_S,
1313 itos(msginfo->msgnum), NULL);
1314 gchar *cache_file = NULL;
1316 cache_path = folder_item_get_path(dest);
1317 cache_file = g_strconcat(
1318 cache_path, G_DIR_SEPARATOR_S,
1320 if (!is_dir_exist(cache_path))
1321 make_dir_hier(cache_path);
1322 if (is_file_exist(real_file) && is_dir_exist(cache_path)) {
1323 copy_file(real_file, cache_file, TRUE);
1324 debug_print("copied to cache: %s\n", cache_file);
1331 g_relation_insert(relation, msginfo,
1332 GINT_TO_POINTER(0));
1333 g_tuples_destroy(tuples);
1335 statusbar_pop_all();
1337 g_relation_destroy(uid_mapping);
1338 imap_lep_set_free(seq_list);
1342 IMAP_FOLDER_ITEM(dest)->lastuid = 0;
1343 IMAP_FOLDER_ITEM(dest)->uid_next = 0;
1344 g_slist_free(IMAP_FOLDER_ITEM(dest)->uid_list);
1345 IMAP_FOLDER_ITEM(dest)->uid_list = NULL;
1348 if (ok == IMAP_SUCCESS)
1354 static gint imap_copy_msg(Folder *folder, FolderItem *dest, MsgInfo *msginfo)
1358 g_return_val_if_fail(msginfo != NULL, -1);
1360 msglist.data = msginfo;
1361 msglist.next = NULL;
1363 return imap_copy_msgs(folder, dest, &msglist, NULL);
1366 static gint imap_copy_msgs(Folder *folder, FolderItem *dest,
1367 MsgInfoList *msglist, GRelation *relation)
1372 g_return_val_if_fail(folder != NULL, -1);
1373 g_return_val_if_fail(dest != NULL, -1);
1374 g_return_val_if_fail(msglist != NULL, -1);
1376 msginfo = (MsgInfo *)msglist->data;
1377 g_return_val_if_fail(msginfo->folder != NULL, -1);
1379 ret = imap_do_copy_msgs(folder, dest, msglist, relation);
1384 static gint imap_do_remove_msgs(Folder *folder, FolderItem *dest,
1385 MsgInfoList *msglist, GRelation *relation)
1387 gchar *destdir, *dir;
1388 GSList *numlist = NULL, *cur;
1390 IMAPSession *session;
1391 gint ok = IMAP_SUCCESS;
1392 GRelation *uid_mapping;
1394 g_return_val_if_fail(folder != NULL, -1);
1395 g_return_val_if_fail(dest != NULL, -1);
1396 g_return_val_if_fail(msglist != NULL, -1);
1398 debug_print("getting session...\n");
1399 session = imap_session_get(folder);
1404 msginfo = (MsgInfo *)msglist->data;
1406 ok = imap_select(session, IMAP_FOLDER(folder), msginfo->folder->path,
1407 NULL, NULL, NULL, NULL, FALSE);
1408 if (ok != IMAP_SUCCESS) {
1413 destdir = imap_get_real_path(session, IMAP_FOLDER(folder), dest->path);
1414 for (cur = msglist; cur; cur = cur->next) {
1415 msginfo = (MsgInfo *)cur->data;
1416 if (!MSG_IS_DELETED(msginfo->flags))
1417 numlist = g_slist_prepend(numlist, GINT_TO_POINTER(msginfo->msgnum));
1419 numlist = g_slist_reverse(numlist);
1421 uid_mapping = g_relation_new(2);
1422 g_relation_index(uid_mapping, 0, g_direct_hash, g_direct_equal);
1424 ok = imap_set_message_flags
1425 (IMAP_SESSION(REMOTE_FOLDER(folder)->session),
1426 numlist, IMAP_FLAG_DELETED, TRUE);
1427 if (ok != IMAP_SUCCESS) {
1428 log_warning(_("can't set deleted flags\n"));
1432 ok = imap_cmd_expunge(session);
1433 if (ok != IMAP_SUCCESS) {
1434 log_warning(_("can't expunge\n"));
1439 dir = folder_item_get_path(msginfo->folder);
1440 if (is_dir_exist(dir)) {
1441 for (cur = msglist; cur; cur = cur->next) {
1442 msginfo = (MsgInfo *)cur->data;
1443 remove_numbered_files(dir, msginfo->msgnum, msginfo->msgnum);
1448 g_relation_destroy(uid_mapping);
1449 g_slist_free(numlist);
1453 if (ok == IMAP_SUCCESS)
1459 static gint imap_remove_msgs(Folder *folder, FolderItem *dest,
1460 MsgInfoList *msglist, GRelation *relation)
1464 g_return_val_if_fail(folder != NULL, -1);
1465 g_return_val_if_fail(dest != NULL, -1);
1466 if (msglist == NULL)
1469 msginfo = (MsgInfo *)msglist->data;
1470 g_return_val_if_fail(msginfo->folder != NULL, -1);
1472 return imap_do_remove_msgs(folder, dest, msglist, relation);
1475 static gint imap_remove_all_msg(Folder *folder, FolderItem *item)
1477 GSList *list = folder_item_get_msg_list(item);
1478 gint res = imap_remove_msgs(folder, item, list, NULL);
1479 procmsg_msg_list_free(list);
1483 static gboolean imap_is_msg_changed(Folder *folder, FolderItem *item,
1486 /* TODO: properly implement this method */
1490 static gint imap_close(Folder *folder, FolderItem *item)
1495 static gint imap_scan_tree(Folder *folder)
1497 FolderItem *item = NULL;
1498 IMAPSession *session;
1499 gchar *root_folder = NULL;
1501 g_return_val_if_fail(folder != NULL, -1);
1502 g_return_val_if_fail(folder->account != NULL, -1);
1504 debug_print("getting session...\n");
1505 session = imap_session_get(folder);
1507 if (!folder->node) {
1508 folder_tree_destroy(folder);
1509 item = folder_item_new(folder, folder->name, NULL);
1510 item->folder = folder;
1511 folder->node = item->node = g_node_new(item);
1516 if (folder->account->imap_dir && *folder->account->imap_dir) {
1521 Xstrdup_a(root_folder, folder->account->imap_dir, {unlock_session();return -1;});
1522 extract_quote(root_folder, '"');
1523 subst_char(root_folder,
1524 imap_get_path_separator(session, IMAP_FOLDER(folder),
1527 strtailchomp(root_folder, '/');
1528 real_path = imap_get_real_path
1529 (session, IMAP_FOLDER(folder), root_folder);
1530 debug_print("IMAP root directory: %s\n", real_path);
1532 /* check if root directory exist */
1534 r = imap_threaded_list(session->folder, "", real_path,
1536 if ((r != MAILIMAP_NO_ERROR) || (clist_count(lep_list) == 0)) {
1537 if (!folder->node) {
1538 item = folder_item_new(folder, folder->name, NULL);
1539 item->folder = folder;
1540 folder->node = item->node = g_node_new(item);
1545 mailimap_list_result_free(lep_list);
1551 item = FOLDER_ITEM(folder->node->data);
1552 if (!item || ((item->path || root_folder) &&
1553 strcmp2(item->path, root_folder) != 0)) {
1554 folder_tree_destroy(folder);
1555 item = folder_item_new(folder, folder->name, root_folder);
1556 item->folder = folder;
1557 folder->node = item->node = g_node_new(item);
1560 imap_scan_tree_recursive(session, FOLDER_ITEM(folder->node->data));
1561 imap_create_missing_folders(folder);
1567 static gint imap_scan_tree_recursive(IMAPSession *session, FolderItem *item)
1570 IMAPFolder *imapfolder;
1571 FolderItem *new_item;
1572 GSList *item_list, *cur;
1575 gchar *wildcard_path;
1581 g_return_val_if_fail(item != NULL, -1);
1582 g_return_val_if_fail(item->folder != NULL, -1);
1583 g_return_val_if_fail(item->no_sub == FALSE, -1);
1585 folder = item->folder;
1586 imapfolder = IMAP_FOLDER(folder);
1588 separator = imap_get_path_separator(session, imapfolder, item->path);
1590 if (folder->ui_func)
1591 folder->ui_func(folder, item, folder->ui_func_data);
1594 wildcard[0] = separator;
1597 real_path = imap_get_real_path(session, imapfolder, item->path);
1601 real_path = g_strdup("");
1604 Xstrcat_a(wildcard_path, real_path, wildcard,
1605 {g_free(real_path); return IMAP_ERROR;});
1607 r = imap_threaded_list(folder, "", wildcard_path, &lep_list);
1608 if (r != MAILIMAP_NO_ERROR) {
1612 item_list = imap_list_from_lep(imapfolder,
1613 lep_list, real_path, FALSE);
1614 mailimap_list_result_free(lep_list);
1619 node = item->node->children;
1620 while (node != NULL) {
1621 FolderItem *old_item = FOLDER_ITEM(node->data);
1622 GNode *next = node->next;
1625 for (cur = item_list; cur != NULL; cur = cur->next) {
1626 FolderItem *cur_item = FOLDER_ITEM(cur->data);
1627 if (!strcmp2(old_item->path, cur_item->path)) {
1628 new_item = cur_item;
1633 debug_print("folder '%s' not found. removing...\n",
1635 folder_item_remove(old_item);
1637 old_item->no_sub = new_item->no_sub;
1638 old_item->no_select = new_item->no_select;
1639 if (old_item->no_sub == TRUE && node->children) {
1640 debug_print("folder '%s' doesn't have "
1641 "subfolders. removing...\n",
1643 folder_item_remove_children(old_item);
1650 for (cur = item_list; cur != NULL; cur = cur->next) {
1651 FolderItem *cur_item = FOLDER_ITEM(cur->data);
1654 for (node = item->node->children; node != NULL;
1655 node = node->next) {
1656 if (!strcmp2(FOLDER_ITEM(node->data)->path,
1658 new_item = FOLDER_ITEM(node->data);
1659 folder_item_destroy(cur_item);
1665 new_item = cur_item;
1666 debug_print("new folder '%s' found.\n", new_item->path);
1667 folder_item_append(item, new_item);
1670 if (!strcmp(new_item->path, "INBOX")) {
1671 new_item->stype = F_INBOX;
1672 folder->inbox = new_item;
1673 } else if (!folder_item_parent(item) || item->stype == F_INBOX) {
1676 base = g_path_get_basename(new_item->path);
1678 if (!folder->outbox && !g_ascii_strcasecmp(base, "Sent")) {
1679 new_item->stype = F_OUTBOX;
1680 folder->outbox = new_item;
1681 } else if (!folder->draft && !g_ascii_strcasecmp(base, "Drafts")) {
1682 new_item->stype = F_DRAFT;
1683 folder->draft = new_item;
1684 } else if (!folder->queue && !g_ascii_strcasecmp(base, "Queue")) {
1685 new_item->stype = F_QUEUE;
1686 folder->queue = new_item;
1687 } else if (!folder->trash && !g_ascii_strcasecmp(base, "Trash")) {
1688 new_item->stype = F_TRASH;
1689 folder->trash = new_item;
1694 if (new_item->no_sub == FALSE)
1695 imap_scan_tree_recursive(session, new_item);
1698 g_slist_free(item_list);
1700 return IMAP_SUCCESS;
1703 static gint imap_create_tree(Folder *folder)
1705 g_return_val_if_fail(folder != NULL, -1);
1706 g_return_val_if_fail(folder->node != NULL, -1);
1707 g_return_val_if_fail(folder->node->data != NULL, -1);
1708 g_return_val_if_fail(folder->account != NULL, -1);
1710 imap_scan_tree(folder);
1711 imap_create_missing_folders(folder);
1716 static void imap_create_missing_folders(Folder *folder)
1718 g_return_if_fail(folder != NULL);
1721 folder->inbox = imap_create_special_folder
1722 (folder, F_INBOX, "INBOX");
1724 folder->trash = imap_create_special_folder
1725 (folder, F_TRASH, "Trash");
1727 folder->queue = imap_create_special_folder
1728 (folder, F_QUEUE, "Queue");
1729 if (!folder->outbox)
1730 folder->outbox = imap_create_special_folder
1731 (folder, F_OUTBOX, "Sent");
1733 folder->draft = imap_create_special_folder
1734 (folder, F_DRAFT, "Drafts");
1737 static FolderItem *imap_create_special_folder(Folder *folder,
1738 SpecialFolderItemType stype,
1742 FolderItem *new_item;
1744 g_return_val_if_fail(folder != NULL, NULL);
1745 g_return_val_if_fail(folder->node != NULL, NULL);
1746 g_return_val_if_fail(folder->node->data != NULL, NULL);
1747 g_return_val_if_fail(folder->account != NULL, NULL);
1748 g_return_val_if_fail(name != NULL, NULL);
1750 item = FOLDER_ITEM(folder->node->data);
1751 new_item = imap_create_folder(folder, item, name);
1754 g_warning("Can't create '%s'\n", name);
1755 if (!folder->inbox) return NULL;
1757 new_item = imap_create_folder(folder, folder->inbox, name);
1759 g_warning("Can't create '%s' under INBOX\n", name);
1761 new_item->stype = stype;
1763 new_item->stype = stype;
1768 static gchar *imap_folder_get_path(Folder *folder)
1772 g_return_val_if_fail(folder != NULL, NULL);
1773 g_return_val_if_fail(folder->account != NULL, NULL);
1775 folder_path = g_strconcat(get_imap_cache_dir(),
1777 folder->account->recv_server,
1779 folder->account->userid,
1785 static gchar *imap_item_get_path(Folder *folder, FolderItem *item)
1787 gchar *folder_path, *path;
1789 g_return_val_if_fail(folder != NULL, NULL);
1790 g_return_val_if_fail(item != NULL, NULL);
1791 folder_path = imap_folder_get_path(folder);
1793 g_return_val_if_fail(folder_path != NULL, NULL);
1794 if (folder_path[0] == G_DIR_SEPARATOR) {
1796 path = g_strconcat(folder_path, G_DIR_SEPARATOR_S,
1799 path = g_strdup(folder_path);
1802 path = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S,
1803 folder_path, G_DIR_SEPARATOR_S,
1806 path = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S,
1809 g_free(folder_path);
1814 static FolderItem *imap_create_folder(Folder *folder, FolderItem *parent,
1817 gchar *dirpath, *imap_path;
1818 IMAPSession *session;
1819 FolderItem *new_item;
1824 gboolean no_select = FALSE, no_sub = FALSE;
1826 g_return_val_if_fail(folder != NULL, NULL);
1827 g_return_val_if_fail(folder->account != NULL, NULL);
1828 g_return_val_if_fail(parent != NULL, NULL);
1829 g_return_val_if_fail(name != NULL, NULL);
1831 debug_print("getting session...\n");
1832 session = imap_session_get(folder);
1837 if (!folder_item_parent(parent) && strcmp(name, "INBOX") == 0) {
1838 dirpath = g_strdup(name);
1839 }else if (parent->path)
1840 dirpath = g_strconcat(parent->path, "/", name, NULL);
1841 else if ((p = strchr(name, '/')) != NULL && *(p + 1) != '\0')
1842 dirpath = g_strdup(name);
1843 else if (folder->account->imap_dir && *folder->account->imap_dir) {
1846 Xstrdup_a(imap_dir, folder->account->imap_dir, {unlock_session();return NULL;});
1847 strtailchomp(imap_dir, '/');
1848 dirpath = g_strconcat(imap_dir, "/", name, NULL);
1850 dirpath = g_strdup(name);
1854 /* keep trailing directory separator to create a folder that contains
1856 imap_path = imap_utf8_to_modified_utf7(dirpath);
1858 strtailchomp(dirpath, '/');
1859 Xstrdup_a(new_name, name, {
1864 separator = imap_get_path_separator(session, IMAP_FOLDER(folder), imap_path);
1865 imap_path_separator_subst(imap_path, separator);
1866 /* remove trailing / for display */
1867 strtailchomp(new_name, '/');
1869 if (strcmp(dirpath, "INBOX") != 0) {
1871 gboolean exist = FALSE;
1875 argbuf = g_ptr_array_new();
1876 r = imap_threaded_list(folder, "", imap_path, &lep_list);
1877 if (r != MAILIMAP_NO_ERROR) {
1878 log_warning(_("can't create mailbox: LIST failed\n"));
1881 ptr_array_free_strings(argbuf);
1882 g_ptr_array_free(argbuf, TRUE);
1887 if (clist_count(lep_list) > 0)
1889 mailimap_list_result_free(lep_list);
1892 ok = imap_cmd_create(session, imap_path);
1893 if (ok != IMAP_SUCCESS) {
1894 log_warning(_("can't create mailbox\n"));
1900 r = imap_threaded_list(folder, "", imap_path, &lep_list);
1901 if (r == MAILIMAP_NO_ERROR) {
1902 GSList *item_list = imap_list_from_lep(IMAP_FOLDER(folder),
1903 lep_list, dirpath, TRUE);
1905 FolderItem *cur_item = FOLDER_ITEM(item_list->data);
1906 no_select = cur_item->no_select;
1907 no_sub = cur_item->no_sub;
1908 g_slist_free(item_list);
1910 mailimap_list_result_free(lep_list);
1917 /* just get flags */
1918 r = imap_threaded_list(folder, "", "INBOX", &lep_list);
1919 if (r == MAILIMAP_NO_ERROR) {
1920 GSList *item_list = imap_list_from_lep(IMAP_FOLDER(folder),
1921 lep_list, dirpath, TRUE);
1923 FolderItem *cur_item = FOLDER_ITEM(item_list->data);
1924 no_select = cur_item->no_select;
1925 no_sub = cur_item->no_sub;
1926 g_slist_free(item_list);
1928 mailimap_list_result_free(lep_list);
1932 new_item = folder_item_new(folder, new_name, dirpath);
1933 new_item->no_select = no_select;
1934 new_item->no_sub = no_sub;
1935 folder_item_append(parent, new_item);
1939 dirpath = folder_item_get_path(new_item);
1940 if (!is_dir_exist(dirpath))
1941 make_dir_hier(dirpath);
1947 static gint imap_rename_folder(Folder *folder, FolderItem *item,
1952 gchar *real_oldpath;
1953 gchar *real_newpath;
1955 gchar *old_cache_dir;
1956 gchar *new_cache_dir;
1957 IMAPSession *session;
1960 gint exists, recent, unseen;
1961 guint32 uid_validity;
1963 g_return_val_if_fail(folder != NULL, -1);
1964 g_return_val_if_fail(item != NULL, -1);
1965 g_return_val_if_fail(item->path != NULL, -1);
1966 g_return_val_if_fail(name != NULL, -1);
1968 debug_print("getting session...\n");
1969 session = imap_session_get(folder);
1974 if (strchr(name, imap_get_path_separator(session, IMAP_FOLDER(folder), item->path)) != NULL) {
1975 g_warning(_("New folder name must not contain the namespace "
1981 real_oldpath = imap_get_real_path(session, IMAP_FOLDER(folder), item->path);
1983 g_free(session->mbox);
1984 session->mbox = NULL;
1985 ok = imap_cmd_examine(session, "INBOX",
1986 &exists, &recent, &unseen, &uid_validity, FALSE);
1987 if (ok != IMAP_SUCCESS) {
1988 g_free(real_oldpath);
1993 separator = imap_get_path_separator(session, IMAP_FOLDER(folder), item->path);
1994 if (strchr(item->path, G_DIR_SEPARATOR)) {
1995 dirpath = g_path_get_dirname(item->path);
1996 newpath = g_strconcat(dirpath, G_DIR_SEPARATOR_S, name, NULL);
1999 newpath = g_strdup(name);
2001 real_newpath = imap_utf8_to_modified_utf7(newpath);
2002 imap_path_separator_subst(real_newpath, separator);
2004 ok = imap_cmd_rename(session, real_oldpath, real_newpath);
2005 if (ok != IMAP_SUCCESS) {
2006 log_warning(_("can't rename mailbox: %s to %s\n"),
2007 real_oldpath, real_newpath);
2008 g_free(real_oldpath);
2010 g_free(real_newpath);
2016 item->name = g_strdup(name);
2018 old_cache_dir = folder_item_get_path(item);
2020 paths[0] = g_strdup(item->path);
2022 g_node_traverse(item->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
2023 imap_rename_folder_func, paths);
2025 if (is_dir_exist(old_cache_dir)) {
2026 new_cache_dir = folder_item_get_path(item);
2027 if (rename(old_cache_dir, new_cache_dir) < 0) {
2028 FILE_OP_ERROR(old_cache_dir, "rename");
2030 g_free(new_cache_dir);
2033 g_free(old_cache_dir);
2036 g_free(real_oldpath);
2037 g_free(real_newpath);
2042 static gint imap_remove_folder_real(Folder *folder, FolderItem *item)
2045 IMAPSession *session;
2049 g_return_val_if_fail(folder != NULL, -1);
2050 g_return_val_if_fail(item != NULL, -1);
2051 g_return_val_if_fail(item->path != NULL, -1);
2053 debug_print("getting session...\n");
2054 session = imap_session_get(folder);
2058 path = imap_get_real_path(session, IMAP_FOLDER(folder), item->path);
2060 ok = imap_cmd_delete(session, path);
2061 if (ok != IMAP_SUCCESS) {
2062 gchar *tmp = g_strdup_printf("%s%c", path,
2063 imap_get_path_separator(session, IMAP_FOLDER(folder), path));
2066 ok = imap_cmd_delete(session, path);
2069 if (ok != IMAP_SUCCESS) {
2070 log_warning(_("can't delete mailbox\n"));
2077 cache_dir = folder_item_get_path(item);
2078 if (is_dir_exist(cache_dir) && remove_dir_recursive(cache_dir) < 0)
2079 g_warning("can't remove directory '%s'\n", cache_dir);
2081 folder_item_remove(item);
2086 static gint imap_remove_folder(Folder *folder, FolderItem *item)
2090 g_return_val_if_fail(item != NULL, -1);
2091 g_return_val_if_fail(item->folder != NULL, -1);
2092 g_return_val_if_fail(item->node != NULL, -1);
2094 node = item->node->children;
2095 while (node != NULL) {
2097 if (imap_remove_folder(folder, FOLDER_ITEM(node->data)) < 0)
2101 debug_print("IMAP removing %s\n", item->path);
2103 if (imap_remove_all_msg(folder, item) < 0)
2105 return imap_remove_folder_real(folder, item);
2108 typedef struct _uncached_data {
2109 IMAPSession *session;
2111 MsgNumberList *numlist;
2117 static void *imap_get_uncached_messages_thread(void *data)
2119 uncached_data *stuff = (uncached_data *)data;
2120 IMAPSession *session = stuff->session;
2121 FolderItem *item = stuff->item;
2122 MsgNumberList *numlist = stuff->numlist;
2124 GSList *newlist = NULL;
2125 GSList *llast = NULL;
2126 GSList *seq_list, *cur;
2128 debug_print("uncached_messages\n");
2130 if (session == NULL || item == NULL || item->folder == NULL
2131 || FOLDER_CLASS(item->folder) != &imap_class) {
2136 seq_list = imap_get_lep_set_from_numlist(numlist);
2137 debug_print("get msgs info\n");
2138 for (cur = seq_list; cur != NULL; cur = g_slist_next(cur)) {
2139 struct mailimap_set * imapset;
2145 imapset = cur->data;
2147 r = imap_threaded_fetch_env(session->folder,
2148 imapset, &env_list);
2149 if (r != MAILIMAP_NO_ERROR)
2152 session_set_access_time(SESSION(session));
2155 for(i = 0 ; i < carray_count(env_list) ; i ++) {
2156 struct imap_fetch_env_info * info;
2159 info = carray_get(env_list, i);
2160 msginfo = imap_envelope_from_lep(info, item);
2161 if (msginfo == NULL)
2163 msginfo->folder = item;
2165 llast = newlist = g_slist_append(newlist, msginfo);
2167 llast = g_slist_append(llast, msginfo);
2168 llast = llast->next;
2173 imap_fetch_env_free(env_list);
2176 for (cur = seq_list; cur != NULL; cur = g_slist_next(cur)) {
2177 struct mailimap_set * imapset;
2179 imapset = cur->data;
2180 mailimap_set_free(imapset);
2183 session_set_access_time(SESSION(session));
2188 #define MAX_MSG_NUM 50
2190 static GSList *imap_get_uncached_messages(IMAPSession *session,
2192 MsgNumberList *numlist)
2194 GSList *result = NULL;
2196 uncached_data *data = g_new0(uncached_data, 1);
2201 data->total = g_slist_length(numlist);
2202 debug_print("messages list : %i\n", data->total);
2204 while (cur != NULL) {
2205 GSList * partial_result;
2213 while (count < MAX_MSG_NUM) {
2218 if (newlist == NULL)
2219 llast = newlist = g_slist_append(newlist, p);
2221 llast = g_slist_append(llast, p);
2222 llast = llast->next;
2232 data->session = session;
2234 data->numlist = newlist;
2237 if (prefs_common.work_offline &&
2238 !inc_offline_should_override(
2239 _("Claws Mail needs network access in order "
2240 "to access the IMAP server."))) {
2246 (GSList *)imap_get_uncached_messages_thread(data);
2248 statusbar_progress_all(data->cur,data->total, 1);
2250 g_slist_free(newlist);
2252 result = g_slist_concat(result, partial_result);
2256 statusbar_progress_all(0,0,0);
2257 statusbar_pop_all();
2262 static void imap_delete_all_cached_messages(FolderItem *item)
2266 g_return_if_fail(item != NULL);
2267 g_return_if_fail(item->folder != NULL);
2268 g_return_if_fail(FOLDER_CLASS(item->folder) == &imap_class);
2270 debug_print("Deleting all cached messages...\n");
2272 dir = folder_item_get_path(item);
2273 if (is_dir_exist(dir))
2274 remove_all_numbered_files(dir);
2277 debug_print("done.\n");
2280 gchar imap_get_path_separator_for_item(FolderItem *item)
2282 Folder *folder = NULL;
2283 IMAPFolder *imap_folder = NULL;
2284 IMAPSession *session = NULL;
2289 folder = item->folder;
2294 imap_folder = IMAP_FOLDER(folder);
2299 debug_print("getting session...");
2300 session = imap_session_get(FOLDER(folder));
2301 result = imap_get_path_separator(session, imap_folder, item->path);
2306 static gchar imap_refresh_path_separator(IMAPSession *session, IMAPFolder *folder, const gchar *subfolder)
2310 gchar separator = '\0';
2312 g_return_val_if_fail(session != NULL, '/');
2313 r = imap_threaded_list((Folder *)folder, "", subfolder, &lep_list);
2315 if (r != MAILIMAP_NO_ERROR) {
2316 log_warning(_("LIST failed\n"));
2320 if (clist_count(lep_list) > 0) {
2321 clistiter * iter = clist_begin(lep_list);
2322 struct mailimap_mailbox_list * mb;
2323 mb = clist_content(iter);
2325 separator = mb->mb_delimiter;
2326 debug_print("got separator: %c\n", folder->last_seen_separator);
2328 mailimap_list_result_free(lep_list);
2332 static gchar imap_get_path_separator(IMAPSession *session, IMAPFolder *folder, const gchar *path)
2334 gchar separator = '/';
2336 if (folder->last_seen_separator == 0) {
2337 folder->last_seen_separator = imap_refresh_path_separator(session, folder, "");
2340 if (folder->last_seen_separator == 0) {
2341 folder->last_seen_separator = imap_refresh_path_separator(session, folder, "INBOX");
2344 if (folder->last_seen_separator != 0) {
2345 debug_print("using separator: %c\n", folder->last_seen_separator);
2346 return folder->last_seen_separator;
2352 static gchar *imap_get_real_path(IMAPSession *session, IMAPFolder *folder, const gchar *path)
2357 g_return_val_if_fail(folder != NULL, NULL);
2358 g_return_val_if_fail(path != NULL, NULL);
2360 real_path = imap_utf8_to_modified_utf7(path);
2361 separator = imap_get_path_separator(session, folder, path);
2362 imap_path_separator_subst(real_path, separator);
2367 static gint imap_set_message_flags(IMAPSession *session,
2368 MsgNumberList *numlist,
2376 seq_list = imap_get_lep_set_from_numlist(numlist);
2378 for(cur = seq_list ; cur != NULL ; cur = g_slist_next(cur)) {
2379 struct mailimap_set * imapset;
2381 imapset = cur->data;
2383 ok = imap_cmd_store(session, imapset,
2387 imap_lep_set_free(seq_list);
2389 return IMAP_SUCCESS;
2392 typedef struct _select_data {
2393 IMAPSession *session;
2398 guint32 *uid_validity;
2402 static gint imap_select(IMAPSession *session, IMAPFolder *folder,
2404 gint *exists, gint *recent, gint *unseen,
2405 guint32 *uid_validity, gboolean block)
2409 gint exists_, recent_, unseen_;
2410 guint32 uid_validity_;
2412 if (!exists && !recent && !unseen && !uid_validity) {
2413 if (session->mbox && strcmp(session->mbox, path) == 0)
2414 return IMAP_SUCCESS;
2423 uid_validity = &uid_validity_;
2425 g_free(session->mbox);
2426 session->mbox = NULL;
2428 real_path = imap_get_real_path(session, folder, path);
2430 ok = imap_cmd_select(session, real_path,
2431 exists, recent, unseen, uid_validity, block);
2432 if (ok != IMAP_SUCCESS)
2433 log_warning(_("can't select folder: %s\n"), real_path);
2435 session->mbox = g_strdup(path);
2436 session->folder_content_changed = FALSE;
2443 static gint imap_status(IMAPSession *session, IMAPFolder *folder,
2444 const gchar *path, IMAPFolderItem *item,
2446 guint32 *uid_next, guint32 *uid_validity,
2447 gint *unseen, gboolean block)
2451 struct mailimap_mailbox_data_status * data_status;
2456 real_path = imap_get_real_path(session, folder, path);
2470 r = imap_threaded_status(FOLDER(folder), real_path,
2471 &data_status, mask);
2474 if (r != MAILIMAP_NO_ERROR) {
2475 debug_print("status err %d\n", r);
2479 if (data_status->st_info_list == NULL) {
2480 mailimap_mailbox_data_status_free(data_status);
2481 debug_print("status->st_info_list == NULL\n");
2486 for(iter = clist_begin(data_status->st_info_list) ; iter != NULL ;
2487 iter = clist_next(iter)) {
2488 struct mailimap_status_info * info;
2490 info = clist_content(iter);
2491 switch (info->st_att) {
2492 case MAILIMAP_STATUS_ATT_MESSAGES:
2493 * messages = info->st_value;
2494 got_values |= 1 << 0;
2497 case MAILIMAP_STATUS_ATT_UIDNEXT:
2498 * uid_next = info->st_value;
2499 got_values |= 1 << 2;
2502 case MAILIMAP_STATUS_ATT_UIDVALIDITY:
2503 * uid_validity = info->st_value;
2504 got_values |= 1 << 3;
2507 case MAILIMAP_STATUS_ATT_UNSEEN:
2508 * unseen = info->st_value;
2509 got_values |= 1 << 4;
2513 mailimap_mailbox_data_status_free(data_status);
2515 if (got_values != mask) {
2516 debug_print("status: incomplete values received (%d)\n", got_values);
2519 return IMAP_SUCCESS;
2522 static void imap_free_capabilities(IMAPSession *session)
2524 slist_free_strings(session->capability);
2525 g_slist_free(session->capability);
2526 session->capability = NULL;
2529 /* low-level IMAP4rev1 commands */
2531 static gint imap_cmd_login(IMAPSession *session,
2532 const gchar *user, const gchar *pass,
2538 if (!strcmp(type, "LOGIN") && imap_has_capability(session, "LOGINDISABLED")) {
2539 gint ok = IMAP_ERROR;
2540 if (imap_has_capability(session, "STARTTLS")) {
2542 log_warning(_("Server requires TLS to log in.\n"));
2543 ok = imap_cmd_starttls(session);
2544 if (ok != IMAP_SUCCESS) {
2545 log_warning(_("Can't start TLS session.\n"));
2549 imap_free_capabilities(session);
2550 if (imap_get_capabilities(session) != MAILIMAP_NO_ERROR) {
2551 log_warning(_("Can't refresh capabilities.\n"));
2556 log_error(_("Connection to %s failed: "
2557 "server requires TLS, but Claws Mail "
2558 "has been compiled without OpenSSL "
2560 SESSION(session)->server);
2564 log_error(_("Server logins are disabled.\n"));
2569 log_print("IMAP4> Logging %s to %s using %s\n",
2571 SESSION(session)->server,
2573 r = imap_threaded_login(session->folder, user, pass, type);
2574 if (r != MAILIMAP_NO_ERROR) {
2575 log_print("IMAP4< Error logging in to %s\n",
2576 SESSION(session)->server);
2579 log_print("IMAP4< Login to %s successful\n",
2580 SESSION(session)->server);
2586 static gint imap_cmd_noop(IMAPSession *session)
2589 unsigned int exists;
2591 r = imap_threaded_noop(session->folder, &exists);
2592 if (r != MAILIMAP_NO_ERROR) {
2593 debug_print("noop err %d\n", r);
2596 session->exists = exists;
2597 session_set_access_time(SESSION(session));
2599 return IMAP_SUCCESS;
2603 static gint imap_cmd_starttls(IMAPSession *session)
2607 r = imap_threaded_starttls(session->folder,
2608 SESSION(session)->server, SESSION(session)->port);
2609 if (r != MAILIMAP_NO_ERROR) {
2610 debug_print("starttls err %d\n", r);
2613 return IMAP_SUCCESS;
2617 static gint imap_cmd_select(IMAPSession *session, const gchar *folder,
2618 gint *exists, gint *recent, gint *unseen,
2619 guint32 *uid_validity, gboolean block)
2623 r = imap_threaded_select(session->folder, folder,
2624 exists, recent, unseen, uid_validity);
2625 if (r != MAILIMAP_NO_ERROR) {
2626 debug_print("select err %d\n", r);
2629 return IMAP_SUCCESS;
2632 static gint imap_cmd_examine(IMAPSession *session, const gchar *folder,
2633 gint *exists, gint *recent, gint *unseen,
2634 guint32 *uid_validity, gboolean block)
2638 r = imap_threaded_examine(session->folder, folder,
2639 exists, recent, unseen, uid_validity);
2640 if (r != MAILIMAP_NO_ERROR) {
2641 debug_print("examine err %d\n", r);
2645 return IMAP_SUCCESS;
2648 static gint imap_cmd_create(IMAPSession *session, const gchar *folder)
2652 r = imap_threaded_create(session->folder, folder);
2653 if (r != MAILIMAP_NO_ERROR) {
2658 return IMAP_SUCCESS;
2661 static gint imap_cmd_rename(IMAPSession *session, const gchar *old_folder,
2662 const gchar *new_folder)
2666 r = imap_threaded_rename(session->folder, old_folder,
2668 if (r != MAILIMAP_NO_ERROR) {
2673 return IMAP_SUCCESS;
2676 static gint imap_cmd_delete(IMAPSession *session, const gchar *folder)
2681 r = imap_threaded_delete(session->folder, folder);
2682 if (r != MAILIMAP_NO_ERROR) {
2687 return IMAP_SUCCESS;
2690 typedef struct _fetch_data {
2691 IMAPSession *session;
2693 const gchar *filename;
2699 static void *imap_cmd_fetch_thread(void *data)
2701 fetch_data *stuff = (fetch_data *)data;
2702 IMAPSession *session = stuff->session;
2703 guint32 uid = stuff->uid;
2704 const gchar *filename = stuff->filename;
2708 r = imap_threaded_fetch_content(session->folder,
2712 r = imap_threaded_fetch_content(session->folder,
2715 if (r != MAILIMAP_NO_ERROR) {
2716 debug_print("fetch err %d\n", r);
2717 return GINT_TO_POINTER(IMAP_ERROR);
2719 return GINT_TO_POINTER(IMAP_SUCCESS);
2722 static gint imap_cmd_fetch(IMAPSession *session, guint32 uid,
2723 const gchar *filename, gboolean headers,
2726 fetch_data *data = g_new0(fetch_data, 1);
2729 data->session = session;
2731 data->filename = filename;
2732 data->headers = headers;
2735 if (prefs_common.work_offline &&
2736 !inc_offline_should_override(
2737 _("Claws Mail needs network access in order "
2738 "to access the IMAP server."))) {
2742 statusbar_print_all(_("Fetching message..."));
2743 result = GPOINTER_TO_INT(imap_cmd_fetch_thread(data));
2744 statusbar_pop_all();
2750 static gint imap_cmd_append(IMAPSession *session, const gchar *destfolder,
2751 const gchar *file, IMAPFlags flags,
2754 struct mailimap_flag_list * flag_list;
2757 g_return_val_if_fail(file != NULL, IMAP_ERROR);
2759 flag_list = imap_flag_to_lep(flags);
2760 r = imap_threaded_append(session->folder, destfolder,
2761 file, flag_list, (int *)new_uid);
2762 mailimap_flag_list_free(flag_list);
2764 if (r != MAILIMAP_NO_ERROR) {
2765 debug_print("append err %d\n", r);
2768 return IMAP_SUCCESS;
2771 static gint imap_cmd_copy(IMAPSession *session, struct mailimap_set * set,
2772 const gchar *destfolder, GRelation *uid_mapping,
2773 struct mailimap_set **source, struct mailimap_set **dest)
2777 g_return_val_if_fail(session != NULL, IMAP_ERROR);
2778 g_return_val_if_fail(set != NULL, IMAP_ERROR);
2779 g_return_val_if_fail(destfolder != NULL, IMAP_ERROR);
2781 r = imap_threaded_copy(session->folder, set, destfolder, source, dest);
2782 if (r != MAILIMAP_NO_ERROR) {
2787 return IMAP_SUCCESS;
2790 static gint imap_cmd_store(IMAPSession *session, struct mailimap_set * set,
2791 IMAPFlags flags, int do_add)
2794 struct mailimap_flag_list * flag_list;
2795 struct mailimap_store_att_flags * store_att_flags;
2797 flag_list = imap_flag_to_lep(flags);
2801 mailimap_store_att_flags_new_add_flags_silent(flag_list);
2804 mailimap_store_att_flags_new_remove_flags_silent(flag_list);
2806 r = imap_threaded_store(session->folder, set, store_att_flags);
2807 mailimap_store_att_flags_free(store_att_flags);
2808 if (r != MAILIMAP_NO_ERROR) {
2813 return IMAP_SUCCESS;
2816 static gint imap_cmd_expunge(IMAPSession *session)
2820 if (prefs_common.work_offline &&
2821 !inc_offline_should_override(
2822 _("Claws Mail needs network access in order "
2823 "to access the IMAP server."))) {
2827 r = imap_threaded_expunge(session->folder);
2828 if (r != MAILIMAP_NO_ERROR) {
2833 return IMAP_SUCCESS;
2836 static void imap_path_separator_subst(gchar *str, gchar separator)
2839 gboolean in_escape = FALSE;
2841 if (!separator || separator == '/') return;
2843 for (p = str; *p != '\0'; p++) {
2844 if (*p == '/' && !in_escape)
2846 else if (*p == '&' && *(p + 1) != '-' && !in_escape)
2848 else if (*p == '-' && in_escape)
2853 static gchar *imap_modified_utf7_to_utf8(const gchar *mutf7_str)
2855 static iconv_t cd = (iconv_t)-1;
2856 static gboolean iconv_ok = TRUE;
2859 size_t norm_utf7_len;
2861 gchar *to_str, *to_p;
2863 gboolean in_escape = FALSE;
2865 if (!iconv_ok) return g_strdup(mutf7_str);
2867 if (cd == (iconv_t)-1) {
2868 cd = iconv_open(CS_INTERNAL, CS_UTF_7);
2869 if (cd == (iconv_t)-1) {
2870 g_warning("iconv cannot convert UTF-7 to %s\n",
2873 return g_strdup(mutf7_str);
2877 /* modified UTF-7 to normal UTF-7 conversion */
2878 norm_utf7 = g_string_new(NULL);
2880 for (p = mutf7_str; *p != '\0'; p++) {
2881 /* replace: '&' -> '+',
2883 escaped ',' -> '/' */
2884 if (!in_escape && *p == '&') {
2885 if (*(p + 1) != '-') {
2886 g_string_append_c(norm_utf7, '+');
2889 g_string_append_c(norm_utf7, '&');
2892 } else if (in_escape && *p == ',') {
2893 g_string_append_c(norm_utf7, '/');
2894 } else if (in_escape && *p == '-') {
2895 g_string_append_c(norm_utf7, '-');
2898 g_string_append_c(norm_utf7, *p);
2902 norm_utf7_p = norm_utf7->str;
2903 norm_utf7_len = norm_utf7->len;
2904 to_len = strlen(mutf7_str) * 5;
2905 to_p = to_str = g_malloc(to_len + 1);
2907 if (iconv(cd, (ICONV_CONST gchar **)&norm_utf7_p, &norm_utf7_len,
2908 &to_p, &to_len) == -1) {
2909 g_warning(_("iconv cannot convert UTF-7 to %s\n"),
2910 conv_get_locale_charset_str());
2911 g_string_free(norm_utf7, TRUE);
2913 return g_strdup(mutf7_str);
2916 /* second iconv() call for flushing */
2917 iconv(cd, NULL, NULL, &to_p, &to_len);
2918 g_string_free(norm_utf7, TRUE);
2924 static gchar *imap_utf8_to_modified_utf7(const gchar *from)
2926 static iconv_t cd = (iconv_t)-1;
2927 static gboolean iconv_ok = TRUE;
2928 gchar *norm_utf7, *norm_utf7_p;
2929 size_t from_len, norm_utf7_len;
2931 gchar *from_tmp, *to, *p;
2932 gboolean in_escape = FALSE;
2934 if (!iconv_ok) return g_strdup(from);
2936 if (cd == (iconv_t)-1) {
2937 cd = iconv_open(CS_UTF_7, CS_INTERNAL);
2938 if (cd == (iconv_t)-1) {
2939 g_warning(_("iconv cannot convert %s to UTF-7\n"),
2942 return g_strdup(from);
2946 /* UTF-8 to normal UTF-7 conversion */
2947 Xstrdup_a(from_tmp, from, return g_strdup(from));
2948 from_len = strlen(from);
2949 norm_utf7_len = from_len * 5;
2950 Xalloca(norm_utf7, norm_utf7_len + 1, return g_strdup(from));
2951 norm_utf7_p = norm_utf7;
2953 #define IS_PRINT(ch) (isprint(ch) && IS_ASCII(ch))
2955 while (from_len > 0) {
2956 if (*from_tmp == '+') {
2957 *norm_utf7_p++ = '+';
2958 *norm_utf7_p++ = '-';
2962 } else if (IS_PRINT(*(guchar *)from_tmp)) {
2963 /* printable ascii char */
2964 *norm_utf7_p = *from_tmp;
2970 size_t conv_len = 0;
2972 /* unprintable char: convert to UTF-7 */
2974 while (!IS_PRINT(*(guchar *)p) && conv_len < from_len) {
2975 conv_len += g_utf8_skip[*(guchar *)p];
2976 p += g_utf8_skip[*(guchar *)p];
2979 from_len -= conv_len;
2980 if (iconv(cd, (ICONV_CONST gchar **)&from_tmp,
2982 &norm_utf7_p, &norm_utf7_len) == -1) {
2983 g_warning(_("iconv cannot convert UTF-8 to UTF-7\n"));
2984 return g_strdup(from);
2987 /* second iconv() call for flushing */
2988 iconv(cd, NULL, NULL, &norm_utf7_p, &norm_utf7_len);
2994 *norm_utf7_p = '\0';
2995 to_str = g_string_new(NULL);
2996 for (p = norm_utf7; p < norm_utf7_p; p++) {
2997 /* replace: '&' -> "&-",
3000 BASE64 '/' -> ',' */
3001 if (!in_escape && *p == '&') {
3002 g_string_append(to_str, "&-");
3003 } else if (!in_escape && *p == '+') {
3004 if (*(p + 1) == '-') {
3005 g_string_append_c(to_str, '+');
3008 g_string_append_c(to_str, '&');
3011 } else if (in_escape && *p == '/') {
3012 g_string_append_c(to_str, ',');
3013 } else if (in_escape && *p == '-') {
3014 g_string_append_c(to_str, '-');
3017 g_string_append_c(to_str, *p);
3023 g_string_append_c(to_str, '-');
3027 g_string_free(to_str, FALSE);
3032 static gboolean imap_rename_folder_func(GNode *node, gpointer data)
3034 FolderItem *item = node->data;
3035 gchar **paths = data;
3036 const gchar *oldpath = paths[0];
3037 const gchar *newpath = paths[1];
3039 gchar *new_itempath;
3042 oldpathlen = strlen(oldpath);
3043 if (strncmp(oldpath, item->path, oldpathlen) != 0) {
3044 g_warning("path doesn't match: %s, %s\n", oldpath, item->path);
3048 base = item->path + oldpathlen;
3049 while (*base == G_DIR_SEPARATOR) base++;
3051 new_itempath = g_strdup(newpath);
3053 new_itempath = g_strconcat(newpath, G_DIR_SEPARATOR_S, base,
3056 item->path = new_itempath;
3061 typedef struct _get_list_uid_data {
3063 IMAPSession *session;
3064 IMAPFolderItem *item;
3065 GSList **msgnum_list;
3067 } get_list_uid_data;
3069 static void *get_list_of_uids_thread(void *data)
3071 get_list_uid_data *stuff = (get_list_uid_data *)data;
3072 Folder *folder = stuff->folder;
3073 IMAPFolderItem *item = stuff->item;
3074 GSList **msgnum_list = stuff->msgnum_list;
3075 gint ok, nummsgs = 0, lastuid_old;
3076 IMAPSession *session;
3077 GSList *uidlist, *elem;
3078 clist * lep_uidlist;
3081 session = stuff->session;
3082 if (session == NULL) {
3084 return GINT_TO_POINTER(-1);
3086 /* no session locking here, it's already locked by caller */
3087 ok = imap_select(session, IMAP_FOLDER(folder), item->item.path,
3088 NULL, NULL, NULL, NULL, TRUE);
3089 if (ok != IMAP_SUCCESS) {
3091 return GINT_TO_POINTER(-1);
3096 r = imap_threaded_search(folder, IMAP_SEARCH_TYPE_SIMPLE, NULL,
3099 if (r == MAILIMAP_NO_ERROR) {
3100 GSList * fetchuid_list;
3103 imap_uid_list_from_lep(lep_uidlist);
3104 mailimap_search_result_free(lep_uidlist);
3106 uidlist = g_slist_concat(fetchuid_list, uidlist);
3109 GSList * fetchuid_list;
3110 carray * lep_uidtab;
3112 r = imap_threaded_fetch_uid(folder, item->lastuid + 1,
3114 if (r == MAILIMAP_NO_ERROR) {
3116 imap_uid_list_from_lep_tab(lep_uidtab);
3117 imap_fetch_uid_list_free(lep_uidtab);
3118 uidlist = g_slist_concat(fetchuid_list, uidlist);
3122 lastuid_old = item->lastuid;
3123 *msgnum_list = g_slist_copy(item->uid_list);
3124 nummsgs = g_slist_length(*msgnum_list);
3125 debug_print("Got %d uids from cache\n", g_slist_length(item->uid_list));
3127 for (elem = uidlist; elem != NULL; elem = g_slist_next(elem)) {
3130 msgnum = GPOINTER_TO_INT(elem->data);
3131 if (msgnum > lastuid_old) {
3132 *msgnum_list = g_slist_prepend(*msgnum_list, GINT_TO_POINTER(msgnum));
3133 item->uid_list = g_slist_prepend(item->uid_list, GINT_TO_POINTER(msgnum));
3136 if(msgnum > item->lastuid)
3137 item->lastuid = msgnum;
3140 g_slist_free(uidlist);
3142 return GINT_TO_POINTER(nummsgs);
3145 static gint get_list_of_uids(IMAPSession *session, Folder *folder, IMAPFolderItem *item, GSList **msgnum_list)
3148 get_list_uid_data *data = g_new0(get_list_uid_data, 1);
3150 data->folder = folder;
3152 data->msgnum_list = msgnum_list;
3153 data->session = session;
3154 if (prefs_common.work_offline &&
3155 !inc_offline_should_override(
3156 _("Claws Mail needs network access in order "
3157 "to access the IMAP server."))) {
3162 result = GPOINTER_TO_INT(get_list_of_uids_thread(data));
3168 gint imap_get_num_list(Folder *folder, FolderItem *_item, GSList **msgnum_list, gboolean *old_uids_valid)
3170 IMAPFolderItem *item = (IMAPFolderItem *)_item;
3171 IMAPSession *session;
3172 gint ok, nummsgs = 0, exists;
3173 guint32 uid_next = 0, uid_val = 0;
3174 GSList *uidlist = NULL;
3176 gboolean selected_folder;
3177 debug_print("get_num_list\n");
3179 g_return_val_if_fail(folder != NULL, -1);
3180 g_return_val_if_fail(item != NULL, -1);
3181 g_return_val_if_fail(item->item.path != NULL, -1);
3182 g_return_val_if_fail(FOLDER_CLASS(folder) == &imap_class, -1);
3183 g_return_val_if_fail(folder->account != NULL, -1);
3185 debug_print("getting session...\n");
3186 session = imap_session_get(folder);
3187 g_return_val_if_fail(session != NULL, -1);
3189 if (FOLDER_ITEM(item)->path)
3190 statusbar_print_all(_("Scanning folder %s%c%s ..."),
3191 FOLDER_ITEM(item)->folder->name,
3193 FOLDER_ITEM(item)->path);
3195 statusbar_print_all(_("Scanning folder %s ..."),
3196 FOLDER_ITEM(item)->folder->name);
3198 selected_folder = (session->mbox != NULL) &&
3199 (!strcmp(session->mbox, item->item.path));
3200 if (selected_folder && time(NULL) - item->use_cache < 2) {
3201 ok = imap_cmd_noop(session);
3202 if (ok != IMAP_SUCCESS) {
3203 debug_print("disconnected!\n");
3204 session = imap_reconnect_if_possible(folder, session);
3205 if (session == NULL) {
3206 statusbar_pop_all();
3211 exists = session->exists;
3213 uid_next = item->c_uid_next;
3214 uid_val = item->c_uid_validity;
3215 *old_uids_valid = TRUE;
3217 if (item->use_cache && time(NULL) - item->use_cache < 2) {
3218 exists = item->c_messages;
3219 uid_next = item->c_uid_next;
3220 uid_val = item->c_uid_validity;
3222 debug_print("using cache %d %d %d\n", exists, uid_next, uid_val);
3224 ok = imap_status(session, IMAP_FOLDER(folder), item->item.path, item,
3225 &exists, &uid_next, &uid_val, NULL, FALSE);
3227 item->item.last_num = uid_next - 1;
3229 item->use_cache = (time_t)0;
3230 if (ok != IMAP_SUCCESS) {
3231 statusbar_pop_all();
3235 if(item->item.mtime == uid_val)
3236 *old_uids_valid = TRUE;
3238 *old_uids_valid = FALSE;
3240 debug_print("Freeing imap uid cache (%d != %d)\n",
3241 (int)item->item.mtime, uid_val);
3243 g_slist_free(item->uid_list);
3244 item->uid_list = NULL;
3246 item->item.mtime = uid_val;
3248 imap_delete_all_cached_messages((FolderItem *)item);
3252 /* If old uid_next matches new uid_next we can be sure no message
3253 was added to the folder */
3254 debug_print("uid_next is %d and item->uid_next %d \n",
3255 uid_next, item->uid_next);
3256 if (uid_next == item->uid_next) {
3257 nummsgs = g_slist_length(item->uid_list);
3259 /* If number of messages is still the same we
3260 know our caches message numbers are still valid,
3261 otherwise if the number of messages has decrease
3262 we discard our cache to start a new scan to find
3263 out which numbers have been removed */
3264 if (exists == nummsgs) {
3265 debug_print("exists == nummsgs\n");
3266 *msgnum_list = g_slist_copy(item->uid_list);
3267 statusbar_pop_all();
3270 } else if (exists < nummsgs) {
3271 debug_print("Freeing imap uid cache");
3273 g_slist_free(item->uid_list);
3274 item->uid_list = NULL;
3279 *msgnum_list = NULL;
3280 statusbar_pop_all();
3285 nummsgs = get_list_of_uids(session, folder, item, &uidlist);
3288 statusbar_pop_all();
3293 if (nummsgs != exists) {
3294 /* Cache contains more messages then folder, we have cached
3295 an old UID of a message that was removed and new messages
3296 have been added too, otherwise the uid_next check would
3298 debug_print("Freeing imap uid cache");
3300 g_slist_free(item->uid_list);
3301 item->uid_list = NULL;
3303 g_slist_free(*msgnum_list);
3305 nummsgs = get_list_of_uids(session, folder, item, &uidlist);
3308 *msgnum_list = uidlist;
3310 dir = folder_item_get_path((FolderItem *)item);
3311 debug_print("removing old messages from %s\n", dir);
3312 remove_numbered_files_not_in_list(dir, *msgnum_list);
3315 item->uid_next = uid_next;
3317 debug_print("get_num_list - ok - %i\n", nummsgs);
3318 statusbar_pop_all();
3323 static MsgInfo *imap_parse_msg(const gchar *file, FolderItem *item)
3328 flags.perm_flags = MSG_NEW|MSG_UNREAD;
3329 flags.tmp_flags = 0;
3331 g_return_val_if_fail(item != NULL, NULL);
3332 g_return_val_if_fail(file != NULL, NULL);
3334 if (folder_has_parent_of_type(item, F_QUEUE)) {
3335 MSG_SET_TMP_FLAGS(flags, MSG_QUEUED);
3336 } else if (folder_has_parent_of_type(item, F_DRAFT)) {
3337 MSG_SET_TMP_FLAGS(flags, MSG_DRAFT);
3340 msginfo = procheader_parse_file(file, flags, FALSE, FALSE);
3341 if (!msginfo) return NULL;
3343 msginfo->plaintext_file = g_strdup(file);
3344 msginfo->folder = item;
3349 GSList *imap_get_msginfos(Folder *folder, FolderItem *item,
3350 GSList *msgnum_list)
3352 IMAPSession *session;
3353 MsgInfoList *ret = NULL;
3356 debug_print("get_msginfos\n");
3358 g_return_val_if_fail(folder != NULL, NULL);
3359 g_return_val_if_fail(item != NULL, NULL);
3360 g_return_val_if_fail(msgnum_list != NULL, NULL);
3362 debug_print("getting session...\n");
3363 session = imap_session_get(folder);
3364 g_return_val_if_fail(session != NULL, NULL);
3366 debug_print("IMAP getting msginfos\n");
3367 ok = imap_select(session, IMAP_FOLDER(folder), item->path,
3368 NULL, NULL, NULL, NULL, FALSE);
3369 if (ok != IMAP_SUCCESS) {
3373 if (!(folder_has_parent_of_type(item, F_DRAFT) ||
3374 folder_has_parent_of_type(item, F_QUEUE))) {
3375 ret = g_slist_concat(ret,
3376 imap_get_uncached_messages(session, item,
3379 MsgNumberList *sorted_list, *elem, *llast = NULL;
3380 gint startnum, lastnum;
3382 sorted_list = g_slist_sort(g_slist_copy(msgnum_list), g_int_compare);
3384 startnum = lastnum = GPOINTER_TO_INT(sorted_list->data);
3386 llast = g_slist_last(ret);
3387 for (elem = sorted_list;; elem = g_slist_next(elem)) {
3391 num = GPOINTER_TO_INT(elem->data);
3393 if (num > lastnum + 1 || elem == NULL) {
3395 for (i = startnum; i <= lastnum; ++i) {
3398 file = imap_fetch_msg(folder, item, i);
3400 MsgInfo *msginfo = imap_parse_msg(file, item);
3401 if (msginfo != NULL) {
3402 msginfo->msgnum = i;
3404 llast = ret = g_slist_append(ret, msginfo);
3406 llast = g_slist_append(llast, msginfo);
3407 llast = llast->next;
3412 session_set_access_time(SESSION(session));
3423 g_slist_free(sorted_list);
3429 MsgInfo *imap_get_msginfo(Folder *folder, FolderItem *item, gint uid)
3431 MsgInfo *msginfo = NULL;
3432 MsgInfoList *msginfolist;
3433 MsgNumberList numlist;
3435 numlist.next = NULL;
3436 numlist.data = GINT_TO_POINTER(uid);
3438 msginfolist = imap_get_msginfos(folder, item, &numlist);
3439 if (msginfolist != NULL) {
3440 msginfo = msginfolist->data;
3441 g_slist_free(msginfolist);
3447 gboolean imap_scan_required(Folder *folder, FolderItem *_item)
3449 IMAPSession *session;
3450 IMAPFolderItem *item = (IMAPFolderItem *)_item;
3451 gint ok, exists = 0, unseen = 0;
3452 guint32 uid_next = 0, uid_val = 0;
3453 gboolean selected_folder;
3455 g_return_val_if_fail(folder != NULL, FALSE);
3456 g_return_val_if_fail(item != NULL, FALSE);
3457 g_return_val_if_fail(item->item.folder != NULL, FALSE);
3458 g_return_val_if_fail(FOLDER_CLASS(item->item.folder) == &imap_class, FALSE);
3460 if (item->item.path == NULL)
3463 debug_print("getting session...\n");
3464 session = imap_session_get(folder);
3465 g_return_val_if_fail(session != NULL, FALSE);
3467 selected_folder = (session->mbox != NULL) &&
3468 (!strcmp(session->mbox, item->item.path));
3469 if (selected_folder && time(NULL) - item->use_cache < 2) {
3470 ok = imap_cmd_noop(session);
3471 if (ok != IMAP_SUCCESS) {
3472 debug_print("disconnected!\n");
3473 session = imap_reconnect_if_possible(folder, session);
3474 if (session == NULL)
3478 if (session->folder_content_changed
3479 || session->exists != item->item.total_msgs) {
3484 ok = imap_status(session, IMAP_FOLDER(folder), item->item.path, IMAP_FOLDER_ITEM(item),
3485 &exists, &uid_next, &uid_val, &unseen, FALSE);
3486 if (ok != IMAP_SUCCESS) {
3491 item->use_cache = time(NULL);
3492 item->c_messages = exists;
3493 item->c_uid_next = uid_next;
3494 item->c_uid_validity = uid_val;
3495 item->c_unseen = unseen;
3496 item->item.last_num = uid_next - 1;
3497 debug_print("uidnext %d, item->uid_next %d, exists %d, item->item.total_msgs %d\n",
3498 uid_next, item->uid_next, exists, item->item.total_msgs);
3499 if ((uid_next != item->uid_next) || (exists != item->item.total_msgs)
3500 || unseen != item->item.unread_msgs || uid_val != item->item.mtime) {
3509 void imap_change_flags(Folder *folder, FolderItem *item, MsgInfo *msginfo, MsgPermFlags newflags)
3511 IMAPSession *session;
3512 IMAPFlags flags_set = 0, flags_unset = 0;
3513 gint ok = IMAP_SUCCESS;
3514 MsgNumberList numlist;
3515 hashtable_data *ht_data = NULL;
3517 g_return_if_fail(folder != NULL);
3518 g_return_if_fail(folder->klass == &imap_class);
3519 g_return_if_fail(item != NULL);
3520 g_return_if_fail(item->folder == folder);
3521 g_return_if_fail(msginfo != NULL);
3522 g_return_if_fail(msginfo->folder == item);
3524 if (!MSG_IS_MARKED(msginfo->flags) && (newflags & MSG_MARKED))
3525 flags_set |= IMAP_FLAG_FLAGGED;
3526 if ( MSG_IS_MARKED(msginfo->flags) && !(newflags & MSG_MARKED))
3527 flags_unset |= IMAP_FLAG_FLAGGED;
3529 if (!MSG_IS_UNREAD(msginfo->flags) && (newflags & MSG_UNREAD))
3530 flags_unset |= IMAP_FLAG_SEEN;
3531 if ( MSG_IS_UNREAD(msginfo->flags) && !(newflags & MSG_UNREAD))
3532 flags_set |= IMAP_FLAG_SEEN;
3534 if (!MSG_IS_REPLIED(msginfo->flags) && (newflags & MSG_REPLIED))
3535 flags_set |= IMAP_FLAG_ANSWERED;
3536 if ( MSG_IS_REPLIED(msginfo->flags) && !(newflags & MSG_REPLIED))
3537 flags_unset |= IMAP_FLAG_ANSWERED;
3539 if (!MSG_IS_DELETED(msginfo->flags) && (newflags & MSG_DELETED))
3540 flags_set |= IMAP_FLAG_DELETED;
3541 if ( MSG_IS_DELETED(msginfo->flags) && !(newflags & MSG_DELETED))
3542 flags_unset |= IMAP_FLAG_DELETED;
3544 if (!flags_set && !flags_unset) {
3545 /* the changed flags were not translatable to IMAP-speak.
3546 * like MSG_POSTFILTERED, so just apply. */
3547 msginfo->flags.perm_flags = newflags;
3551 debug_print("getting session...\n");
3552 session = imap_session_get(folder);
3557 if ((ok = imap_select(session, IMAP_FOLDER(folder), msginfo->folder->path,
3558 NULL, NULL, NULL, NULL, FALSE)) != IMAP_SUCCESS) {
3562 numlist.next = NULL;
3563 numlist.data = GINT_TO_POINTER(msginfo->msgnum);
3565 if (IMAP_FOLDER_ITEM(item)->batching) {
3566 /* instead of performing an UID STORE command for each message change,
3567 * as a lot of them can change "together", we just fill in hashtables
3568 * and defer the treatment so that we're able to send only one
3571 debug_print("IMAP batch mode on, deferring flags change\n");
3573 ht_data = g_hash_table_lookup(IMAP_FOLDER_ITEM(item)->flags_set_table,
3574 GINT_TO_POINTER(flags_set));
3575 if (ht_data == NULL) {
3576 ht_data = g_new0(hashtable_data, 1);
3577 ht_data->session = session;
3578 ht_data->item = IMAP_FOLDER_ITEM(item);
3579 g_hash_table_insert(IMAP_FOLDER_ITEM(item)->flags_set_table,
3580 GINT_TO_POINTER(flags_set), ht_data);
3582 if (!g_slist_find(ht_data->msglist, GINT_TO_POINTER(msginfo->msgnum)))
3583 ht_data->msglist = g_slist_prepend(ht_data->msglist, GINT_TO_POINTER(msginfo->msgnum));
3586 ht_data = g_hash_table_lookup(IMAP_FOLDER_ITEM(item)->flags_unset_table,
3587 GINT_TO_POINTER(flags_unset));
3588 if (ht_data == NULL) {
3589 ht_data = g_new0(hashtable_data, 1);
3590 ht_data->session = session;
3591 ht_data->item = IMAP_FOLDER_ITEM(item);
3592 g_hash_table_insert(IMAP_FOLDER_ITEM(item)->flags_unset_table,
3593 GINT_TO_POINTER(flags_unset), ht_data);
3595 if (!g_slist_find(ht_data->msglist, GINT_TO_POINTER(msginfo->msgnum)))
3596 ht_data->msglist = g_slist_prepend(ht_data->msglist,
3597 GINT_TO_POINTER(msginfo->msgnum));
3600 debug_print("IMAP changing flags\n");
3602 ok = imap_set_message_flags(session, &numlist, flags_set, TRUE);
3603 if (ok != IMAP_SUCCESS) {
3610 ok = imap_set_message_flags(session, &numlist, flags_unset, FALSE);
3611 if (ok != IMAP_SUCCESS) {
3617 msginfo->flags.perm_flags = newflags;
3622 static gint imap_remove_msg(Folder *folder, FolderItem *item, gint uid)
3625 IMAPSession *session;
3627 MsgNumberList numlist;
3629 g_return_val_if_fail(folder != NULL, -1);
3630 g_return_val_if_fail(FOLDER_CLASS(folder) == &imap_class, -1);
3631 g_return_val_if_fail(item != NULL, -1);
3633 debug_print("getting session...\n");
3634 session = imap_session_get(folder);
3635 if (!session) return -1;
3637 ok = imap_select(session, IMAP_FOLDER(folder), item->path,
3638 NULL, NULL, NULL, NULL, FALSE);
3639 if (ok != IMAP_SUCCESS) {
3643 numlist.next = NULL;
3644 numlist.data = GINT_TO_POINTER(uid);
3646 ok = imap_set_message_flags
3647 (IMAP_SESSION(REMOTE_FOLDER(folder)->session),
3648 &numlist, IMAP_FLAG_DELETED, TRUE);
3649 if (ok != IMAP_SUCCESS) {
3650 log_warning(_("can't set deleted flags: %d\n"), uid);
3655 if (!session->uidplus) {
3656 ok = imap_cmd_expunge(session);
3660 uidstr = g_strdup_printf("%u", uid);
3661 ok = imap_cmd_expunge(session);
3664 if (ok != IMAP_SUCCESS) {
3665 log_warning(_("can't expunge\n"));
3670 IMAP_FOLDER_ITEM(item)->uid_list = g_slist_remove(
3671 IMAP_FOLDER_ITEM(item)->uid_list, numlist.data);
3672 dir = folder_item_get_path(item);
3673 if (is_dir_exist(dir))
3674 remove_numbered_files(dir, uid, uid);
3677 return IMAP_SUCCESS;
3680 static gint compare_msginfo(gconstpointer a, gconstpointer b)
3682 return ((MsgInfo *)a)->msgnum - ((MsgInfo *)b)->msgnum;
3685 static guint gslist_find_next_num(MsgNumberList **list, guint num)
3689 g_return_val_if_fail(list != NULL, -1);
3691 for (elem = *list; elem != NULL; elem = g_slist_next(elem))
3692 if (GPOINTER_TO_INT(elem->data) >= num)
3695 return elem != NULL ? GPOINTER_TO_INT(elem->data) : (gint)-1;
3699 * NEW and DELETED flags are not syncronized
3700 * - The NEW/RECENT flags in IMAP folders can not really be directly
3701 * modified by Sylpheed
3702 * - The DELETE/DELETED flag in IMAP and Sylpheed don't have the same
3703 * meaning, in IMAP it always removes the messages from the FolderItem
3704 * in Sylpheed it can mean to move the message to trash
3707 typedef struct _get_flags_data {
3710 MsgInfoList *msginfo_list;
3711 GRelation *msgflags;
3712 gboolean full_search;
3716 static /*gint*/ void *imap_get_flags_thread(void *data)
3718 get_flags_data *stuff = (get_flags_data *)data;
3719 Folder *folder = stuff->folder;
3720 FolderItem *item = stuff->item;
3721 MsgInfoList *msginfo_list = stuff->msginfo_list;
3722 GRelation *msgflags = stuff->msgflags;
3723 gboolean full_search = stuff->full_search;
3724 IMAPSession *session;
3725 GSList *sorted_list = NULL;
3726 GSList *unseen = NULL, *answered = NULL, *flagged = NULL, *deleted = NULL;
3727 GSList *p_unseen, *p_answered, *p_flagged, *p_deleted;
3729 GSList *seq_list, *cur;
3730 gboolean reverse_seen = FALSE;
3733 gint exists_cnt, unseen_cnt;
3734 gboolean selected_folder;
3736 if (folder == NULL || item == NULL) {
3738 return GINT_TO_POINTER(-1);
3741 debug_print("getting session...\n");
3742 session = imap_session_get(folder);
3743 if (session == NULL) {
3745 return GINT_TO_POINTER(-1);
3748 selected_folder = (session->mbox != NULL) &&
3749 (!strcmp(session->mbox, item->path));
3751 if (!selected_folder) {
3752 ok = imap_select(session, IMAP_FOLDER(folder), item->path,
3753 &exists_cnt, NULL, &unseen_cnt, NULL, TRUE);
3754 if (ok != IMAP_SUCCESS) {
3757 return GINT_TO_POINTER(-1);
3760 if (unseen_cnt > exists_cnt / 2)
3761 reverse_seen = TRUE;
3764 if (item->unread_msgs > item->total_msgs / 2)
3765 reverse_seen = TRUE;
3768 cmd_buf = g_string_new(NULL);
3770 sorted_list = g_slist_sort(g_slist_copy(msginfo_list), compare_msginfo);
3772 seq_list = imap_get_lep_set_from_msglist(msginfo_list);
3774 struct mailimap_set * set;
3775 set = mailimap_set_new_interval(1, 0);
3776 seq_list = g_slist_append(NULL, set);
3779 for (cur = seq_list; cur != NULL; cur = g_slist_next(cur)) {
3780 struct mailimap_set * imapset;
3781 clist * lep_uidlist;
3784 imapset = cur->data;
3786 r = imap_threaded_search(folder, IMAP_SEARCH_TYPE_SEEN,
3787 full_search ? NULL:imapset, &lep_uidlist);
3790 r = imap_threaded_search(folder,
3791 IMAP_SEARCH_TYPE_UNSEEN,
3792 full_search ? NULL:imapset, &lep_uidlist);
3794 if (r == MAILIMAP_NO_ERROR) {
3797 uidlist = imap_uid_list_from_lep(lep_uidlist);
3798 mailimap_search_result_free(lep_uidlist);
3800 unseen = g_slist_concat(unseen, uidlist);
3803 r = imap_threaded_search(folder, IMAP_SEARCH_TYPE_FLAGGED,
3804 full_search ? NULL:imapset, &lep_uidlist);
3805 if (r == MAILIMAP_NO_ERROR) {
3808 uidlist = imap_uid_list_from_lep(lep_uidlist);
3809 mailimap_search_result_free(lep_uidlist);
3811 flagged = g_slist_concat(flagged, uidlist);
3814 if (item->opened || item->processing_pending || item == folder->inbox) {
3815 r = imap_threaded_search(folder, IMAP_SEARCH_TYPE_ANSWERED,
3816 full_search ? NULL:imapset, &lep_uidlist);
3817 if (r == MAILIMAP_NO_ERROR) {
3820 uidlist = imap_uid_list_from_lep(lep_uidlist);
3821 mailimap_search_result_free(lep_uidlist);
3823 answered = g_slist_concat(answered, uidlist);
3826 r = imap_threaded_search(folder, IMAP_SEARCH_TYPE_DELETED,
3827 full_search ? NULL:imapset, &lep_uidlist);
3828 if (r == MAILIMAP_NO_ERROR) {
3831 uidlist = imap_uid_list_from_lep(lep_uidlist);
3832 mailimap_search_result_free(lep_uidlist);
3834 deleted = g_slist_concat(deleted, uidlist);
3840 p_answered = answered;
3841 p_flagged = flagged;
3842 p_deleted = deleted;
3844 for (elem = sorted_list; elem != NULL; elem = g_slist_next(elem)) {
3849 msginfo = (MsgInfo *) elem->data;
3850 flags = msginfo->flags.perm_flags;
3851 wasnew = (flags & MSG_NEW);
3852 if (item->opened || item->processing_pending || item == folder->inbox) {
3853 flags &= ~((reverse_seen ? 0 : MSG_UNREAD | MSG_NEW) | MSG_REPLIED | MSG_MARKED);
3855 flags &= ~((reverse_seen ? 0 : MSG_UNREAD | MSG_NEW | MSG_MARKED));
3858 flags |= MSG_UNREAD | (wasnew ? MSG_NEW : 0);
3859 if (gslist_find_next_num(&p_unseen, msginfo->msgnum) == msginfo->msgnum) {
3860 if (!reverse_seen) {
3861 flags |= MSG_UNREAD | (wasnew ? MSG_NEW : 0);
3863 flags &= ~(MSG_UNREAD | MSG_NEW);
3867 if (gslist_find_next_num(&p_flagged, msginfo->msgnum) == msginfo->msgnum)
3868 flags |= MSG_MARKED;
3870 flags &= ~MSG_MARKED;
3872 if (item->opened || item->processing_pending || item == folder->inbox) {
3873 if (gslist_find_next_num(&p_answered, msginfo->msgnum) == msginfo->msgnum)
3874 flags |= MSG_REPLIED;
3876 flags &= ~MSG_REPLIED;
3877 if (gslist_find_next_num(&p_deleted, msginfo->msgnum) == msginfo->msgnum)
3878 flags |= MSG_DELETED;
3880 flags &= ~MSG_DELETED;
3882 g_relation_insert(msgflags, msginfo, GINT_TO_POINTER(flags));
3885 imap_lep_set_free(seq_list);
3886 g_slist_free(flagged);
3887 g_slist_free(deleted);
3888 g_slist_free(answered);
3889 g_slist_free(unseen);
3890 g_slist_free(sorted_list);
3891 g_string_free(cmd_buf, TRUE);
3895 return GINT_TO_POINTER(0);
3898 static gint imap_get_flags(Folder *folder, FolderItem *item,
3899 MsgInfoList *msginfo_list, GRelation *msgflags)
3902 get_flags_data *data = g_new0(get_flags_data, 1);
3904 data->folder = folder;
3906 data->msginfo_list = msginfo_list;
3907 data->msgflags = msgflags;
3908 data->full_search = FALSE;
3910 GSList *tmp = NULL, *cur;
3912 if (prefs_common.work_offline &&
3913 !inc_offline_should_override(
3914 _("Claws Mail needs network access in order "
3915 "to access the IMAP server."))) {
3920 tmp = folder_item_get_msg_list(item);
3922 if (g_slist_length(tmp) <= g_slist_length(msginfo_list))
3923 data->full_search = TRUE;
3925 for (cur = tmp; cur; cur = cur->next)
3926 procmsg_msginfo_free((MsgInfo *)cur->data);
3930 result = GPOINTER_TO_INT(imap_get_flags_thread(data));
3937 static gboolean process_flags(gpointer key, gpointer value, gpointer user_data)
3939 gboolean flags_set = GPOINTER_TO_INT(user_data);
3940 gint flags_value = GPOINTER_TO_INT(key);
3941 hashtable_data *data = (hashtable_data *)value;
3942 IMAPFolderItem *_item = data->item;
3943 FolderItem *item = (FolderItem *)_item;
3944 gint ok = IMAP_ERROR;
3945 IMAPSession *session = NULL;
3947 debug_print("getting session...\n");
3948 session = imap_session_get(item->folder);
3950 data->msglist = g_slist_reverse(data->msglist);
3952 debug_print("IMAP %ssetting flags to %d for %d messages\n",
3955 g_slist_length(data->msglist));
3958 ok = imap_select(session, IMAP_FOLDER(item->folder), item->path,
3959 NULL, NULL, NULL, NULL, FALSE);
3961 if (ok == IMAP_SUCCESS) {
3962 imap_set_message_flags(data->session, data->msglist, flags_value, flags_set);
3964 g_warning("can't select mailbox %s\n", item->path);
3968 g_slist_free(data->msglist);
3973 static void process_hashtable(IMAPFolderItem *item)
3975 if (item->flags_set_table) {
3976 g_hash_table_foreach_remove(item->flags_set_table, process_flags, GINT_TO_POINTER(TRUE));
3977 g_hash_table_destroy(item->flags_set_table);
3978 item->flags_set_table = NULL;
3980 if (item->flags_unset_table) {
3981 g_hash_table_foreach_remove(item->flags_unset_table, process_flags, GINT_TO_POINTER(FALSE));
3982 g_hash_table_destroy(item->flags_unset_table);
3983 item->flags_unset_table = NULL;
3987 static void imap_set_batch (Folder *folder, FolderItem *_item, gboolean batch)
3989 IMAPFolderItem *item = (IMAPFolderItem *)_item;
3991 g_return_if_fail(item != NULL);
3993 if (item->batching == batch)
3997 item->batching = TRUE;
3998 debug_print("IMAP switching to batch mode\n");
3999 if (!item->flags_set_table) {
4000 item->flags_set_table = g_hash_table_new(NULL, g_direct_equal);
4002 if (!item->flags_unset_table) {
4003 item->flags_unset_table = g_hash_table_new(NULL, g_direct_equal);
4006 debug_print("IMAP switching away from batch mode\n");
4008 process_hashtable(item);
4009 item->batching = FALSE;
4015 /* data types conversion libetpan <-> claws */
4019 #define ETPAN_IMAP_MB_MARKED 1
4020 #define ETPAN_IMAP_MB_UNMARKED 2
4021 #define ETPAN_IMAP_MB_NOSELECT 4
4022 #define ETPAN_IMAP_MB_NOINFERIORS 8
4024 static int imap_flags_to_flags(struct mailimap_mbx_list_flags * imap_flags)
4030 if (imap_flags->mbf_type == MAILIMAP_MBX_LIST_FLAGS_SFLAG) {
4031 switch (imap_flags->mbf_sflag) {
4032 case MAILIMAP_MBX_LIST_SFLAG_MARKED:
4033 flags |= ETPAN_IMAP_MB_MARKED;
4035 case MAILIMAP_MBX_LIST_SFLAG_NOSELECT:
4036 flags |= ETPAN_IMAP_MB_NOSELECT;
4038 case MAILIMAP_MBX_LIST_SFLAG_UNMARKED:
4039 flags |= ETPAN_IMAP_MB_UNMARKED;
4044 for(cur = clist_begin(imap_flags->mbf_oflags) ; cur != NULL ;
4045 cur = clist_next(cur)) {
4046 struct mailimap_mbx_list_oflag * oflag;
4048 oflag = clist_content(cur);
4050 switch (oflag->of_type) {
4051 case MAILIMAP_MBX_LIST_OFLAG_NOINFERIORS:
4052 flags |= ETPAN_IMAP_MB_NOINFERIORS;
4060 static GSList * imap_list_from_lep(IMAPFolder * folder,
4061 clist * list, const gchar * real_path, gboolean all)
4064 GSList * item_list = NULL, *llast = NULL;
4066 for(iter = clist_begin(list) ; iter != NULL ;
4067 iter = clist_next(iter)) {
4068 struct mailimap_mailbox_list * mb;
4076 FolderItem *new_item;
4078 mb = clist_content(iter);
4084 if (mb->mb_flag != NULL)
4085 flags = imap_flags_to_flags(mb->mb_flag);
4087 delimiter = mb->mb_delimiter;
4090 dup_name = strdup(name);
4091 if (delimiter != '\0')
4092 subst_char(dup_name, delimiter, '/');
4094 base = g_path_get_basename(dup_name);
4095 if (base[0] == '.') {
4101 if (!all && path_cmp(name, real_path) == 0) {
4107 if (!all && dup_name[strlen(dup_name)-1] == '/') {
4108 dup_name[strlen(dup_name)-1] = '\0';
4111 loc_name = imap_modified_utf7_to_utf8(base);
4112 loc_path = imap_modified_utf7_to_utf8(dup_name);
4114 new_item = folder_item_new(FOLDER(folder), loc_name, loc_path);
4115 if ((flags & ETPAN_IMAP_MB_NOINFERIORS) != 0)
4116 new_item->no_sub = TRUE;
4117 if (strcmp(dup_name, "INBOX") != 0 &&
4118 ((flags & ETPAN_IMAP_MB_NOSELECT) != 0))
4119 new_item->no_select = TRUE;
4121 if (item_list == NULL)
4122 llast = item_list = g_slist_append(item_list, new_item);
4124 llast = g_slist_append(llast, new_item);
4125 llast = llast->next;
4127 debug_print("folder '%s' found.\n", loc_path);
4138 static GSList * imap_get_lep_set_from_numlist(MsgNumberList *numlist)
4140 GSList *sorted_list, *cur;
4141 guint first, last, next;
4142 GSList *ret_list = NULL, *llast = NULL;
4144 struct mailimap_set * current_set;
4145 unsigned int item_count;
4147 if (numlist == NULL)
4151 current_set = mailimap_set_new_empty();
4153 sorted_list = g_slist_copy(numlist);
4154 sorted_list = g_slist_sort(sorted_list, g_int_compare);
4156 first = GPOINTER_TO_INT(sorted_list->data);
4159 for (cur = sorted_list; cur != NULL; cur = g_slist_next(cur)) {
4160 if (GPOINTER_TO_INT(cur->data) == 0)
4165 last = GPOINTER_TO_INT(cur->data);
4167 next = GPOINTER_TO_INT(cur->next->data);
4171 if (last + 1 != next || next == 0) {
4173 struct mailimap_set_item * item;
4174 item = mailimap_set_item_new(first, last);
4175 mailimap_set_add(current_set, item);
4180 if (count >= IMAP_SET_MAX_COUNT) {
4181 if (ret_list == NULL)
4182 llast = ret_list = g_slist_append(ret_list,
4185 llast = g_slist_append(llast, current_set);
4186 llast = llast->next;
4188 current_set = mailimap_set_new_empty();
4195 if (clist_count(current_set->set_list) > 0) {
4196 ret_list = g_slist_append(ret_list,
4200 g_slist_free(sorted_list);
4205 static GSList * imap_get_lep_set_from_msglist(MsgInfoList *msglist)
4207 MsgNumberList *numlist = NULL;
4211 for (cur = msglist; cur != NULL; cur = g_slist_next(cur)) {
4212 MsgInfo *msginfo = (MsgInfo *) cur->data;
4214 numlist = g_slist_prepend(numlist, GINT_TO_POINTER(msginfo->msgnum));
4216 numlist = g_slist_reverse(numlist);
4217 seq_list = imap_get_lep_set_from_numlist(numlist);
4218 g_slist_free(numlist);
4223 static GSList * imap_uid_list_from_lep(clist * list)
4230 for(iter = clist_begin(list) ; iter != NULL ;
4231 iter = clist_next(iter)) {
4234 puid = clist_content(iter);
4235 result = g_slist_prepend(result, GINT_TO_POINTER(* puid));
4238 result = g_slist_reverse(result);
4242 static GSList * imap_uid_list_from_lep_tab(carray * list)
4249 for(i = 0 ; i < carray_count(list) ; i ++) {
4252 puid = carray_get(list, i);
4253 result = g_slist_prepend(result, GINT_TO_POINTER(* puid));
4255 result = g_slist_reverse(result);
4259 static MsgInfo *imap_envelope_from_lep(struct imap_fetch_env_info * info,
4262 MsgInfo *msginfo = NULL;
4265 MsgFlags flags = {0, 0};
4267 if (info->headers == NULL)
4270 MSG_SET_TMP_FLAGS(flags, MSG_IMAP);
4271 if (folder_has_parent_of_type(item, F_QUEUE)) {
4272 MSG_SET_TMP_FLAGS(flags, MSG_QUEUED);
4273 } else if (folder_has_parent_of_type(item, F_DRAFT)) {
4274 MSG_SET_TMP_FLAGS(flags, MSG_DRAFT);
4276 flags.perm_flags = info->flags;
4280 msginfo = procheader_parse_str(info->headers, flags, FALSE, FALSE);
4283 msginfo->msgnum = uid;
4284 msginfo->size = size;
4290 static void imap_lep_set_free(GSList *seq_list)
4294 for(cur = seq_list ; cur != NULL ; cur = g_slist_next(cur)) {
4295 struct mailimap_set * imapset;
4297 imapset = cur->data;
4298 mailimap_set_free(imapset);
4300 g_slist_free(seq_list);
4303 static struct mailimap_flag_list * imap_flag_to_lep(IMAPFlags flags)
4305 struct mailimap_flag_list * flag_list;
4307 flag_list = mailimap_flag_list_new_empty();
4309 if (IMAP_IS_SEEN(flags))
4310 mailimap_flag_list_add(flag_list,
4311 mailimap_flag_new_seen());
4312 if (IMAP_IS_ANSWERED(flags))
4313 mailimap_flag_list_add(flag_list,
4314 mailimap_flag_new_answered());
4315 if (IMAP_IS_FLAGGED(flags))
4316 mailimap_flag_list_add(flag_list,
4317 mailimap_flag_new_flagged());
4318 if (IMAP_IS_DELETED(flags))
4319 mailimap_flag_list_add(flag_list,
4320 mailimap_flag_new_deleted());
4321 if (IMAP_IS_DRAFT(flags))
4322 mailimap_flag_list_add(flag_list,
4323 mailimap_flag_new_draft());
4328 guint imap_folder_get_refcnt(Folder *folder)
4330 return ((IMAPFolder *)folder)->refcnt;
4333 void imap_folder_ref(Folder *folder)
4335 ((IMAPFolder *)folder)->refcnt++;
4338 void imap_disconnect_all(void)
4341 for (list = account_get_list(); list != NULL; list = list->next) {
4342 PrefsAccount *account = list->data;
4343 if (account->protocol == A_IMAP4) {
4344 RemoteFolder *folder = (RemoteFolder *)account->folder;
4345 if (folder && folder->session) {
4346 IMAPSession *session = (IMAPSession *)folder->session;
4347 imap_threaded_disconnect(FOLDER(folder));
4348 SESSION(session)->state = SESSION_DISCONNECTED;
4349 session_destroy(SESSION(session));
4350 folder->session = NULL;
4356 void imap_folder_unref(Folder *folder)
4358 if (((IMAPFolder *)folder)->refcnt > 0)
4359 ((IMAPFolder *)folder)->refcnt--;
4362 #else /* HAVE_LIBETPAN */
4364 static FolderClass imap_class;
4366 static XMLTag *imap_item_get_xml(Folder *folder, FolderItem *item);
4367 static void imap_item_set_xml(Folder *folder, FolderItem *item, XMLTag *tag);
4369 static Folder *imap_folder_new (const gchar *name,
4372 static gboolean missing_imap_warning = TRUE;
4373 if (missing_imap_warning) {
4374 missing_imap_warning = FALSE;
4376 _("You have one or more IMAP accounts "
4377 "defined. However this version of "
4378 "Claws Mail has been built without "
4379 "IMAP support; your IMAP account(s) are "
4381 "You probably need to "
4382 "install libetpan and recompile "
4387 static gint imap_create_tree (Folder *folder)
4391 static FolderItem *imap_create_folder (Folder *folder,
4397 static gint imap_rename_folder (Folder *folder,
4404 gchar imap_get_path_separator_for_item(FolderItem *item)
4409 FolderClass *imap_get_class(void)
4411 if (imap_class.idstr == NULL) {
4412 imap_class.type = F_IMAP;
4413 imap_class.idstr = "imap";
4414 imap_class.uistr = "IMAP4";
4416 imap_class.new_folder = imap_folder_new;
4417 imap_class.create_tree = imap_create_tree;
4418 imap_class.create_folder = imap_create_folder;
4419 imap_class.rename_folder = imap_rename_folder;
4421 imap_class.set_xml = folder_set_xml;
4422 imap_class.get_xml = folder_get_xml;
4423 imap_class.item_set_xml = imap_item_set_xml;
4424 imap_class.item_get_xml = imap_item_get_xml;
4425 /* nothing implemented */
4431 void imap_disconnect_all(void)
4437 void imap_synchronise(FolderItem *item)
4439 imap_gtk_synchronise(item);
4442 static void imap_item_set_xml(Folder *folder, FolderItem *item, XMLTag *tag)
4444 #ifdef HAVE_LIBETPAN
4447 folder_item_set_xml(folder, item, tag);
4449 #ifdef HAVE_LIBETPAN
4450 for (cur = tag->attr; cur != NULL; cur = g_list_next(cur)) {
4451 XMLAttr *attr = (XMLAttr *) cur->data;
4453 if (!attr || !attr->name || !attr->value) continue;
4454 if (!strcmp(attr->name, "uidnext"))
4455 IMAP_FOLDER_ITEM(item)->uid_next = atoi(attr->value);
4460 static XMLTag *imap_item_get_xml(Folder *folder, FolderItem *item)
4464 tag = folder_item_get_xml(folder, item);
4466 #ifdef HAVE_LIBETPAN
4467 xml_tag_add_attr(tag, xml_attr_new_int("uidnext",
4468 IMAP_FOLDER_ITEM(item)->uid_next));