2 * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2006 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 IMAPNameSpace *imap_find_namespace (IMAPFolder *folder,
298 static gchar imap_get_path_separator (IMAPFolder *folder,
300 static gchar *imap_get_real_path (IMAPFolder *folder,
302 static void imap_synchronise (FolderItem *item);
304 static void imap_free_capabilities (IMAPSession *session);
306 /* low-level IMAP4rev1 commands */
307 static gint imap_cmd_login (IMAPSession *session,
311 static gint imap_cmd_noop (IMAPSession *session);
313 static gint imap_cmd_starttls (IMAPSession *session);
315 static gint imap_cmd_select (IMAPSession *session,
320 guint32 *uid_validity,
322 static gint imap_cmd_examine (IMAPSession *session,
327 guint32 *uid_validity,
329 static gint imap_cmd_create (IMAPSession *sock,
330 const gchar *folder);
331 static gint imap_cmd_rename (IMAPSession *sock,
332 const gchar *oldfolder,
333 const gchar *newfolder);
334 static gint imap_cmd_delete (IMAPSession *session,
335 const gchar *folder);
336 static gint imap_cmd_fetch (IMAPSession *sock,
338 const gchar *filename,
341 static gint imap_cmd_append (IMAPSession *session,
342 const gchar *destfolder,
346 static gint imap_cmd_copy (IMAPSession *session,
347 struct mailimap_set * set,
348 const gchar *destfolder,
349 GRelation *uid_mapping,
350 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 session = imap_session_get(folder);
679 rfolder->session = SESSION(session);
685 #define lock_session() {\
686 debug_print("locking session\n"); \
687 session->busy = TRUE;\
690 #define unlock_session() {\
691 debug_print("unlocking session\n"); \
692 session->busy = FALSE;\
695 static IMAPSession *imap_session_get(Folder *folder)
697 RemoteFolder *rfolder = REMOTE_FOLDER(folder);
698 IMAPSession *session = NULL;
700 g_return_val_if_fail(folder != NULL, NULL);
701 g_return_val_if_fail(FOLDER_CLASS(folder) == &imap_class, NULL);
702 g_return_val_if_fail(folder->account != NULL, NULL);
704 if (prefs_common.work_offline &&
705 !inc_offline_should_override(
706 _("Claws Mail needs network access in order "
707 "to access the IMAP server."))) {
711 /* Make sure we have a session */
712 if (rfolder->session != NULL) {
713 session = IMAP_SESSION(rfolder->session);
714 /* don't do that yet...
719 imap_reset_uid_lists(folder);
720 if (time(NULL) - rfolder->last_failure <= 2)
722 session = imap_session_new(folder, folder->account);
724 if(session == NULL) {
725 rfolder->last_failure = time(NULL);
729 /* Make sure session is authenticated */
730 if (!IMAP_SESSION(session)->authenticated)
731 imap_session_authenticate(IMAP_SESSION(session), folder->account);
733 if (!IMAP_SESSION(session)->authenticated) {
734 imap_threaded_disconnect(session->folder);
735 SESSION(session)->state = SESSION_DISCONNECTED;
736 session_destroy(SESSION(session));
737 rfolder->session = NULL;
738 rfolder->last_failure = time(NULL);
742 /* I think the point of this code is to avoid sending a
743 * keepalive if we've used the session recently and therefore
744 * think it's still alive. Unfortunately, most of the code
745 * does not yet check for errors on the socket, and so if the
746 * connection drops we don't notice until the timeout expires.
747 * A better solution than sending a NOOP every time would be
748 * for every command to be prepared to retry until it is
749 * successfully sent. -- mbp */
750 if (time(NULL) - SESSION(session)->last_access_time > SESSION_TIMEOUT_INTERVAL) {
751 /* verify that the session is still alive */
752 if (imap_cmd_noop(session) != IMAP_SUCCESS) {
753 debug_print("disconnected!\n");
754 session = imap_reconnect_if_possible(folder, session);
758 rfolder->session = SESSION(session);
760 return IMAP_SESSION(session);
763 static IMAPSession *imap_session_new(Folder * folder,
764 const PrefsAccount *account)
766 IMAPSession *session;
769 int authenticated = FALSE;
772 /* FIXME: IMAP over SSL only... */
775 port = account->set_imapport ? account->imapport
776 : account->ssl_imap == SSL_TUNNEL ? IMAPS_PORT : IMAP4_PORT;
777 ssl_type = account->ssl_imap;
779 if (account->ssl_imap != SSL_NONE) {
780 if (alertpanel_full(_("Insecure connection"),
781 _("This connection is configured to be secured "
782 "using SSL, but SSL is not available in this "
783 "build of Claws Mail. \n\n"
784 "Do you want to continue connecting to this "
785 "server? The communication would not be "
787 GTK_STOCK_CANCEL, _("Con_tinue connecting"),
788 NULL, FALSE, NULL, ALERT_WARNING,
789 G_ALERTDEFAULT) != G_ALERTALTERNATE)
792 port = account->set_imapport ? account->imapport
797 statusbar_print_all(_("Connecting to IMAP4 server: %s..."), folder->account->recv_server);
798 if (account->set_tunnelcmd) {
799 r = imap_threaded_connect_cmd(folder,
801 account->recv_server,
806 if (ssl_type == SSL_TUNNEL) {
807 r = imap_threaded_connect_ssl(folder,
808 account->recv_server,
814 r = imap_threaded_connect(folder,
815 account->recv_server,
821 if (r == MAILIMAP_NO_ERROR_AUTHENTICATED) {
822 authenticated = TRUE;
824 else if (r == MAILIMAP_NO_ERROR_NON_AUTHENTICATED) {
825 authenticated = FALSE;
828 #if (LIBETPAN_VERSION_MAJOR > 0 || LIBETPAN_VERSION_MINOR > 48)
830 if (r == MAILIMAP_ERROR_SSL)
831 log_error(_("SSL handshake failed\n"));
834 if(!prefs_common.no_recv_err_panel) {
835 alertpanel_error_log(_("Can't connect to IMAP4 server: %s:%d"),
836 account->recv_server, port);
838 log_error(_("Can't connect to IMAP4 server: %s:%d\n"),
839 account->recv_server, port);
845 session = g_new0(IMAPSession, 1);
846 session_init(SESSION(session));
847 SESSION(session)->type = SESSION_IMAP;
848 SESSION(session)->server = g_strdup(account->recv_server);
849 SESSION(session)->sock = NULL;
851 SESSION(session)->destroy = imap_session_destroy;
853 session->capability = NULL;
855 session->authenticated = authenticated;
856 session->mbox = NULL;
857 session->cmd_count = 0;
858 session->folder = folder;
859 IMAP_FOLDER(session->folder)->last_seen_separator = 0;
862 if (account->ssl_imap == SSL_STARTTLS) {
865 ok = imap_cmd_starttls(session);
866 if (ok != IMAP_SUCCESS) {
867 log_warning(_("Can't start TLS session.\n"));
868 session_destroy(SESSION(session));
872 imap_free_capabilities(session);
873 session->authenticated = FALSE;
874 session->uidplus = FALSE;
875 session->cmd_count = 1;
878 log_message("IMAP connection is %s-authenticated\n",
879 (session->authenticated) ? "pre" : "un");
884 static void imap_session_authenticate(IMAPSession *session,
885 const PrefsAccount *account)
887 gchar *pass, *acc_pass;
888 gboolean failed = FALSE;
890 g_return_if_fail(account->userid != NULL);
891 acc_pass = account->passwd;
894 if (!pass && account->imap_auth_type != IMAP_AUTH_ANON) {
896 tmp_pass = input_dialog_query_password(account->recv_server, account->userid);
899 Xstrdup_a(pass, tmp_pass, {g_free(tmp_pass); return;});
901 } else if (account->imap_auth_type == IMAP_AUTH_ANON) {
904 statusbar_print_all(_("Connecting to IMAP4 server %s...\n"),
905 account->recv_server);
906 if (imap_auth(session, account->userid, pass, account->imap_auth_type) != IMAP_SUCCESS) {
914 if (prefs_common.no_recv_err_panel) {
915 log_error(_("Couldn't login to IMAP server %s."), account->recv_server);
916 mainwindow_show_error();
918 alertpanel_error_log(_("Couldn't login to IMAP server %s."), account->recv_server);
925 session->authenticated = TRUE;
929 static void imap_session_destroy(Session *session)
931 if (session->state != SESSION_DISCONNECTED)
932 imap_threaded_disconnect(IMAP_SESSION(session)->folder);
934 imap_free_capabilities(IMAP_SESSION(session));
935 g_free(IMAP_SESSION(session)->mbox);
936 sock_close(session->sock);
937 session->sock = NULL;
940 static gchar *imap_fetch_msg(Folder *folder, FolderItem *item, gint uid)
942 return imap_fetch_msg_full(folder, item, uid, TRUE, TRUE);
945 static guint get_size_with_crs(MsgInfo *info)
954 fp = procmsg_open_message(info);
958 while (fgets(buf, sizeof (buf), fp) != NULL) {
960 if (!strstr(buf, "\r") && strstr(buf, "\n"))
968 static gchar *imap_fetch_msg_full(Folder *folder, FolderItem *item, gint uid,
969 gboolean headers, gboolean body)
971 gchar *path, *filename;
972 IMAPSession *session;
975 g_return_val_if_fail(folder != NULL, NULL);
976 g_return_val_if_fail(item != NULL, NULL);
981 path = folder_item_get_path(item);
982 if (!is_dir_exist(path))
984 filename = g_strconcat(path, G_DIR_SEPARATOR_S, itos(uid), NULL);
986 debug_print("trying to fetch cached %s\n", filename);
987 if (is_file_exist(filename)) {
988 /* see whether the local file represents the whole message
989 * or not. As the IMAP server reports size with \r chars,
990 * we have to update the local file (UNIX \n only) size */
991 MsgInfo *msginfo = imap_parse_msg(filename, item);
992 MsgInfo *cached = msgcache_get_msg(item->cache,uid);
993 guint have_size = get_size_with_crs(msginfo);
996 debug_print("message %d has been already %scached (%d/%d).\n", uid,
997 have_size >= cached->size ? "fully ":"",
998 have_size, (int)cached->size);
1000 if (cached && (cached->size <= have_size || !body)) {
1001 procmsg_msginfo_free(cached);
1002 procmsg_msginfo_free(msginfo);
1003 file_strip_crs(filename);
1005 } else if (!cached && time(NULL) - get_file_mtime(filename) < 60) {
1006 debug_print("message not cached and file recent, considering file complete\n");
1007 procmsg_msginfo_free(msginfo);
1008 file_strip_crs(filename);
1011 procmsg_msginfo_free(cached);
1012 procmsg_msginfo_free(msginfo);
1016 session = imap_session_get(folder);
1025 debug_print("IMAP fetching messages\n");
1026 ok = imap_select(session, IMAP_FOLDER(folder), item->path,
1027 NULL, NULL, NULL, NULL, FALSE);
1028 if (ok != IMAP_SUCCESS) {
1029 g_warning("can't select mailbox %s\n", item->path);
1035 debug_print("getting message %d...\n", uid);
1036 ok = imap_cmd_fetch(session, (guint32)uid, filename, headers, body);
1038 if (ok != IMAP_SUCCESS) {
1039 g_warning("can't fetch message %d\n", uid);
1046 file_strip_crs(filename);
1050 static gint imap_add_msg(Folder *folder, FolderItem *dest,
1051 const gchar *file, MsgFlags *flags)
1055 MsgFileInfo fileinfo;
1057 g_return_val_if_fail(file != NULL, -1);
1059 fileinfo.msginfo = NULL;
1060 fileinfo.file = (gchar *)file;
1061 fileinfo.flags = flags;
1062 file_list.data = &fileinfo;
1063 file_list.next = NULL;
1065 ret = imap_add_msgs(folder, dest, &file_list, NULL);
1069 static gint imap_add_msgs(Folder *folder, FolderItem *dest, GSList *file_list,
1070 GRelation *relation)
1073 IMAPSession *session;
1074 guint32 last_uid = 0;
1076 MsgFileInfo *fileinfo;
1078 gint curnum = 0, total = 0;
1081 g_return_val_if_fail(folder != NULL, -1);
1082 g_return_val_if_fail(dest != NULL, -1);
1083 g_return_val_if_fail(file_list != NULL, -1);
1085 session = imap_session_get(folder);
1090 destdir = imap_get_real_path(IMAP_FOLDER(folder), dest->path);
1092 statusbar_print_all(_("Adding messages..."));
1093 total = g_slist_length(file_list);
1094 for (cur = file_list; cur != NULL; cur = cur->next) {
1095 IMAPFlags iflags = 0;
1096 guint32 new_uid = 0;
1097 gchar *real_file = NULL;
1098 fileinfo = (MsgFileInfo *)cur->data;
1100 statusbar_progress_all(curnum, total, 1);
1103 if (fileinfo->flags) {
1104 if (MSG_IS_MARKED(*fileinfo->flags))
1105 iflags |= IMAP_FLAG_FLAGGED;
1106 if (MSG_IS_REPLIED(*fileinfo->flags))
1107 iflags |= IMAP_FLAG_ANSWERED;
1108 if (!MSG_IS_UNREAD(*fileinfo->flags))
1109 iflags |= IMAP_FLAG_SEEN;
1112 if (real_file == NULL)
1113 real_file = g_strdup(fileinfo->file);
1115 if (folder_has_parent_of_type(dest, F_QUEUE) ||
1116 folder_has_parent_of_type(dest, F_OUTBOX) ||
1117 folder_has_parent_of_type(dest, F_DRAFT) ||
1118 folder_has_parent_of_type(dest, F_TRASH))
1119 iflags |= IMAP_FLAG_SEEN;
1121 ok = imap_cmd_append(session, destdir, real_file, iflags,
1124 if (ok != IMAP_SUCCESS) {
1125 g_warning("can't append message %s\n", real_file);
1129 statusbar_progress_all(0,0,0);
1130 statusbar_pop_all();
1133 debug_print("appended new message as %d\n", new_uid);
1134 /* put the local file in the imapcache, so that we don't
1135 * have to fetch it back later. */
1137 gchar *cache_path = folder_item_get_path(dest);
1138 if (!is_dir_exist(cache_path))
1139 make_dir_hier(cache_path);
1140 if (is_dir_exist(cache_path)) {
1141 gchar *cache_file = g_strconcat(
1142 cache_path, G_DIR_SEPARATOR_S,
1143 itos(new_uid), NULL);
1144 copy_file(real_file, cache_file, TRUE);
1145 debug_print("copied to cache: %s\n", cache_file);
1152 if (relation != NULL)
1153 g_relation_insert(relation, fileinfo->msginfo != NULL ?
1154 (gpointer) fileinfo->msginfo : (gpointer) fileinfo,
1155 GINT_TO_POINTER(dest->last_num + 1));
1157 new_uid = dest->last_num+1;
1159 if (last_uid < new_uid) {
1165 statusbar_progress_all(0,0,0);
1166 statusbar_pop_all();
1168 imap_cmd_expunge(session);
1176 static gint imap_do_copy_msgs(Folder *folder, FolderItem *dest,
1177 MsgInfoList *msglist, GRelation *relation)
1181 GSList *seq_list, *cur;
1183 IMAPSession *session;
1184 gint ok = IMAP_SUCCESS;
1185 GRelation *uid_mapping;
1187 gboolean single = FALSE;
1189 g_return_val_if_fail(folder != NULL, -1);
1190 g_return_val_if_fail(dest != NULL, -1);
1191 g_return_val_if_fail(msglist != NULL, -1);
1193 session = imap_session_get(folder);
1199 msginfo = (MsgInfo *)msglist->data;
1200 if (msglist->next == NULL)
1202 src = msginfo->folder;
1204 g_warning("the src folder is identical to the dest.\n");
1209 if (src->folder != dest->folder) {
1210 GSList *infolist = NULL, *cur;
1212 for (cur = msglist; cur; cur = cur->next) {
1213 msginfo = (MsgInfo *)cur->data;
1214 MsgFileInfo *fileinfo = g_new0(MsgFileInfo, 1);
1215 fileinfo->file = procmsg_get_message_file(msginfo);
1216 fileinfo->flags = &(msginfo->flags);
1217 infolist = g_slist_prepend(infolist, fileinfo);
1219 infolist = g_slist_reverse(infolist);
1220 res = folder_item_add_msgs(dest, infolist, FALSE);
1221 for (cur = infolist; cur; cur = cur->next) {
1222 MsgFileInfo *info = (MsgFileInfo *)cur->data;
1226 g_slist_free(infolist);
1230 ok = imap_select(session, IMAP_FOLDER(folder), msginfo->folder->path,
1231 NULL, NULL, NULL, NULL, FALSE);
1232 if (ok != IMAP_SUCCESS) {
1237 destdir = imap_get_real_path(IMAP_FOLDER(folder), dest->path);
1238 seq_list = imap_get_lep_set_from_msglist(msglist);
1239 uid_mapping = g_relation_new(2);
1240 g_relation_index(uid_mapping, 0, g_direct_hash, g_direct_equal);
1242 statusbar_print_all(_("Copying messages..."));
1243 for (cur = seq_list; cur != NULL; cur = g_slist_next(cur)) {
1244 struct mailimap_set * seq_set;
1245 struct mailimap_set * source = NULL;
1246 struct mailimap_set * dest = NULL;
1247 seq_set = cur->data;
1249 debug_print("Copying messages from %s to %s ...\n",
1250 src->path, destdir);
1252 ok = imap_cmd_copy(session, seq_set, destdir, uid_mapping,
1255 if (ok == IMAP_SUCCESS) {
1256 if (single && relation && source && dest) {
1257 clistiter *l = clist_begin(source->set_list);
1258 struct mailimap_set_item *i = (struct mailimap_set_item *)clist_content(l);
1259 int snum = i->set_first;
1261 l = clist_begin(dest->set_list);
1262 i = (struct mailimap_set_item *)clist_content(l);
1263 dnum = i->set_first;
1264 g_relation_insert(uid_mapping, GINT_TO_POINTER(snum),
1265 GINT_TO_POINTER(dnum));
1271 mailimap_set_free(source);
1273 mailimap_set_free(dest);
1275 if (ok != IMAP_SUCCESS) {
1276 g_relation_destroy(uid_mapping);
1277 imap_lep_set_free(seq_list);
1283 for (cur = msglist; cur != NULL; cur = g_slist_next(cur)) {
1284 MsgInfo *msginfo = (MsgInfo *)cur->data;
1287 tuples = g_relation_select(uid_mapping,
1288 GINT_TO_POINTER(msginfo->msgnum),
1290 if (tuples->len > 0) {
1291 gint num = GPOINTER_TO_INT(g_tuples_index(tuples, 0, 1));
1292 g_relation_insert(relation, msginfo,
1293 GINT_TO_POINTER(num));
1296 debug_print("copied new message as %d\n", num);
1297 /* put the local file in the imapcache, so that we don't
1298 * have to fetch it back later. */
1300 gchar *cache_path = folder_item_get_path(msginfo->folder);
1301 gchar *real_file = g_strconcat(
1302 cache_path, G_DIR_SEPARATOR_S,
1303 itos(msginfo->msgnum), NULL);
1304 gchar *cache_file = NULL;
1306 cache_path = folder_item_get_path(dest);
1307 cache_file = g_strconcat(
1308 cache_path, G_DIR_SEPARATOR_S,
1310 if (!is_dir_exist(cache_path))
1311 make_dir_hier(cache_path);
1312 if (is_file_exist(real_file) && is_dir_exist(cache_path)) {
1313 copy_file(real_file, cache_file, TRUE);
1314 debug_print("copied to cache: %s\n", cache_file);
1321 g_relation_insert(relation, msginfo,
1322 GINT_TO_POINTER(0));
1323 g_tuples_destroy(tuples);
1325 statusbar_pop_all();
1327 g_relation_destroy(uid_mapping);
1328 imap_lep_set_free(seq_list);
1332 IMAP_FOLDER_ITEM(dest)->lastuid = 0;
1333 IMAP_FOLDER_ITEM(dest)->uid_next = 0;
1334 g_slist_free(IMAP_FOLDER_ITEM(dest)->uid_list);
1335 IMAP_FOLDER_ITEM(dest)->uid_list = NULL;
1338 if (ok == IMAP_SUCCESS)
1344 static gint imap_copy_msg(Folder *folder, FolderItem *dest, MsgInfo *msginfo)
1348 g_return_val_if_fail(msginfo != NULL, -1);
1350 msglist.data = msginfo;
1351 msglist.next = NULL;
1353 return imap_copy_msgs(folder, dest, &msglist, NULL);
1356 static gint imap_copy_msgs(Folder *folder, FolderItem *dest,
1357 MsgInfoList *msglist, GRelation *relation)
1362 g_return_val_if_fail(folder != NULL, -1);
1363 g_return_val_if_fail(dest != NULL, -1);
1364 g_return_val_if_fail(msglist != NULL, -1);
1366 msginfo = (MsgInfo *)msglist->data;
1367 g_return_val_if_fail(msginfo->folder != NULL, -1);
1369 ret = imap_do_copy_msgs(folder, dest, msglist, relation);
1374 static gint imap_do_remove_msgs(Folder *folder, FolderItem *dest,
1375 MsgInfoList *msglist, GRelation *relation)
1377 gchar *destdir, *dir;
1378 GSList *numlist = NULL, *cur;
1380 IMAPSession *session;
1381 gint ok = IMAP_SUCCESS;
1382 GRelation *uid_mapping;
1384 g_return_val_if_fail(folder != NULL, -1);
1385 g_return_val_if_fail(dest != NULL, -1);
1386 g_return_val_if_fail(msglist != NULL, -1);
1388 session = imap_session_get(folder);
1393 msginfo = (MsgInfo *)msglist->data;
1395 ok = imap_select(session, IMAP_FOLDER(folder), msginfo->folder->path,
1396 NULL, NULL, NULL, NULL, FALSE);
1397 if (ok != IMAP_SUCCESS) {
1402 destdir = imap_get_real_path(IMAP_FOLDER(folder), dest->path);
1403 for (cur = msglist; cur; cur = cur->next) {
1404 msginfo = (MsgInfo *)cur->data;
1405 if (!MSG_IS_DELETED(msginfo->flags))
1406 numlist = g_slist_prepend(numlist, GINT_TO_POINTER(msginfo->msgnum));
1408 numlist = g_slist_reverse(numlist);
1410 uid_mapping = g_relation_new(2);
1411 g_relation_index(uid_mapping, 0, g_direct_hash, g_direct_equal);
1413 ok = imap_set_message_flags
1414 (IMAP_SESSION(REMOTE_FOLDER(folder)->session),
1415 numlist, IMAP_FLAG_DELETED, TRUE);
1416 if (ok != IMAP_SUCCESS) {
1417 log_warning(_("can't set deleted flags\n"));
1421 ok = imap_cmd_expunge(session);
1422 if (ok != IMAP_SUCCESS) {
1423 log_warning(_("can't expunge\n"));
1428 dir = folder_item_get_path(msginfo->folder);
1429 if (is_dir_exist(dir)) {
1430 for (cur = msglist; cur; cur = cur->next) {
1431 msginfo = (MsgInfo *)cur->data;
1432 remove_numbered_files(dir, msginfo->msgnum, msginfo->msgnum);
1437 g_relation_destroy(uid_mapping);
1438 g_slist_free(numlist);
1442 if (ok == IMAP_SUCCESS)
1448 static gint imap_remove_msgs(Folder *folder, FolderItem *dest,
1449 MsgInfoList *msglist, GRelation *relation)
1453 g_return_val_if_fail(folder != NULL, -1);
1454 g_return_val_if_fail(dest != NULL, -1);
1455 if (msglist == NULL)
1458 msginfo = (MsgInfo *)msglist->data;
1459 g_return_val_if_fail(msginfo->folder != NULL, -1);
1461 return imap_do_remove_msgs(folder, dest, msglist, relation);
1464 static gint imap_remove_all_msg(Folder *folder, FolderItem *item)
1466 GSList *list = folder_item_get_msg_list(item);
1467 gint res = imap_remove_msgs(folder, item, list, NULL);
1468 procmsg_msg_list_free(list);
1472 static gboolean imap_is_msg_changed(Folder *folder, FolderItem *item,
1475 /* TODO: properly implement this method */
1479 static gint imap_close(Folder *folder, FolderItem *item)
1484 static gint imap_scan_tree(Folder *folder)
1486 FolderItem *item = NULL;
1487 IMAPSession *session;
1488 gchar *root_folder = NULL;
1490 g_return_val_if_fail(folder != NULL, -1);
1491 g_return_val_if_fail(folder->account != NULL, -1);
1493 session = imap_session_get(folder);
1495 if (!folder->node) {
1496 folder_tree_destroy(folder);
1497 item = folder_item_new(folder, folder->name, NULL);
1498 item->folder = folder;
1499 folder->node = item->node = g_node_new(item);
1505 if (folder->account->imap_dir && *folder->account->imap_dir) {
1510 Xstrdup_a(root_folder, folder->account->imap_dir, {unlock_session();return -1;});
1511 extract_quote(root_folder, '"');
1512 subst_char(root_folder,
1513 imap_get_path_separator(IMAP_FOLDER(folder),
1516 strtailchomp(root_folder, '/');
1517 real_path = imap_get_real_path
1518 (IMAP_FOLDER(folder), root_folder);
1519 debug_print("IMAP root directory: %s\n", real_path);
1521 /* check if root directory exist */
1523 r = imap_threaded_list(session->folder, "", real_path,
1525 if ((r != MAILIMAP_NO_ERROR) || (clist_count(lep_list) == 0)) {
1526 if (!folder->node) {
1527 item = folder_item_new(folder, folder->name, NULL);
1528 item->folder = folder;
1529 folder->node = item->node = g_node_new(item);
1534 mailimap_list_result_free(lep_list);
1540 item = FOLDER_ITEM(folder->node->data);
1541 if (!item || ((item->path || root_folder) &&
1542 strcmp2(item->path, root_folder) != 0)) {
1543 folder_tree_destroy(folder);
1544 item = folder_item_new(folder, folder->name, root_folder);
1545 item->folder = folder;
1546 folder->node = item->node = g_node_new(item);
1549 imap_scan_tree_recursive(session, FOLDER_ITEM(folder->node->data));
1550 imap_create_missing_folders(folder);
1556 static gint imap_scan_tree_recursive(IMAPSession *session, FolderItem *item)
1559 IMAPFolder *imapfolder;
1560 FolderItem *new_item;
1561 GSList *item_list, *cur;
1564 gchar *wildcard_path;
1570 g_return_val_if_fail(item != NULL, -1);
1571 g_return_val_if_fail(item->folder != NULL, -1);
1572 g_return_val_if_fail(item->no_sub == FALSE, -1);
1574 folder = item->folder;
1575 imapfolder = IMAP_FOLDER(folder);
1577 separator = imap_get_path_separator(imapfolder, item->path);
1579 if (folder->ui_func)
1580 folder->ui_func(folder, item, folder->ui_func_data);
1583 wildcard[0] = separator;
1586 real_path = imap_get_real_path(imapfolder, item->path);
1590 real_path = g_strdup("");
1593 Xstrcat_a(wildcard_path, real_path, wildcard,
1594 {g_free(real_path); return IMAP_ERROR;});
1596 r = imap_threaded_list(folder, "", wildcard_path, &lep_list);
1597 if (r != MAILIMAP_NO_ERROR) {
1601 item_list = imap_list_from_lep(imapfolder,
1602 lep_list, real_path, FALSE);
1603 mailimap_list_result_free(lep_list);
1608 node = item->node->children;
1609 while (node != NULL) {
1610 FolderItem *old_item = FOLDER_ITEM(node->data);
1611 GNode *next = node->next;
1614 for (cur = item_list; cur != NULL; cur = cur->next) {
1615 FolderItem *cur_item = FOLDER_ITEM(cur->data);
1616 if (!strcmp2(old_item->path, cur_item->path)) {
1617 new_item = cur_item;
1622 debug_print("folder '%s' not found. removing...\n",
1624 folder_item_remove(old_item);
1626 old_item->no_sub = new_item->no_sub;
1627 old_item->no_select = new_item->no_select;
1628 if (old_item->no_sub == TRUE && node->children) {
1629 debug_print("folder '%s' doesn't have "
1630 "subfolders. removing...\n",
1632 folder_item_remove_children(old_item);
1639 for (cur = item_list; cur != NULL; cur = cur->next) {
1640 FolderItem *cur_item = FOLDER_ITEM(cur->data);
1643 for (node = item->node->children; node != NULL;
1644 node = node->next) {
1645 if (!strcmp2(FOLDER_ITEM(node->data)->path,
1647 new_item = FOLDER_ITEM(node->data);
1648 folder_item_destroy(cur_item);
1654 new_item = cur_item;
1655 debug_print("new folder '%s' found.\n", new_item->path);
1656 folder_item_append(item, new_item);
1659 if (!strcmp(new_item->path, "INBOX")) {
1660 new_item->stype = F_INBOX;
1661 folder->inbox = new_item;
1662 } else if (!folder_item_parent(item) || item->stype == F_INBOX) {
1665 base = g_path_get_basename(new_item->path);
1667 if (!folder->outbox && !g_ascii_strcasecmp(base, "Sent")) {
1668 new_item->stype = F_OUTBOX;
1669 folder->outbox = new_item;
1670 } else if (!folder->draft && !g_ascii_strcasecmp(base, "Drafts")) {
1671 new_item->stype = F_DRAFT;
1672 folder->draft = new_item;
1673 } else if (!folder->queue && !g_ascii_strcasecmp(base, "Queue")) {
1674 new_item->stype = F_QUEUE;
1675 folder->queue = new_item;
1676 } else if (!folder->trash && !g_ascii_strcasecmp(base, "Trash")) {
1677 new_item->stype = F_TRASH;
1678 folder->trash = new_item;
1683 if (new_item->no_sub == FALSE)
1684 imap_scan_tree_recursive(session, new_item);
1687 g_slist_free(item_list);
1689 return IMAP_SUCCESS;
1692 static gint imap_create_tree(Folder *folder)
1694 g_return_val_if_fail(folder != NULL, -1);
1695 g_return_val_if_fail(folder->node != NULL, -1);
1696 g_return_val_if_fail(folder->node->data != NULL, -1);
1697 g_return_val_if_fail(folder->account != NULL, -1);
1699 imap_scan_tree(folder);
1700 imap_create_missing_folders(folder);
1705 static void imap_create_missing_folders(Folder *folder)
1707 g_return_if_fail(folder != NULL);
1710 folder->inbox = imap_create_special_folder
1711 (folder, F_INBOX, "INBOX");
1713 folder->trash = imap_create_special_folder
1714 (folder, F_TRASH, "Trash");
1716 folder->queue = imap_create_special_folder
1717 (folder, F_QUEUE, "Queue");
1718 if (!folder->outbox)
1719 folder->outbox = imap_create_special_folder
1720 (folder, F_OUTBOX, "Sent");
1722 folder->draft = imap_create_special_folder
1723 (folder, F_DRAFT, "Drafts");
1726 static FolderItem *imap_create_special_folder(Folder *folder,
1727 SpecialFolderItemType stype,
1731 FolderItem *new_item;
1733 g_return_val_if_fail(folder != NULL, NULL);
1734 g_return_val_if_fail(folder->node != NULL, NULL);
1735 g_return_val_if_fail(folder->node->data != NULL, NULL);
1736 g_return_val_if_fail(folder->account != NULL, NULL);
1737 g_return_val_if_fail(name != NULL, NULL);
1739 item = FOLDER_ITEM(folder->node->data);
1740 new_item = imap_create_folder(folder, item, name);
1743 g_warning("Can't create '%s'\n", name);
1744 if (!folder->inbox) return NULL;
1746 new_item = imap_create_folder(folder, folder->inbox, name);
1748 g_warning("Can't create '%s' under INBOX\n", name);
1750 new_item->stype = stype;
1752 new_item->stype = stype;
1757 static gchar *imap_folder_get_path(Folder *folder)
1761 g_return_val_if_fail(folder != NULL, NULL);
1762 g_return_val_if_fail(folder->account != NULL, NULL);
1764 folder_path = g_strconcat(get_imap_cache_dir(),
1766 folder->account->recv_server,
1768 folder->account->userid,
1774 static gchar *imap_item_get_path(Folder *folder, FolderItem *item)
1776 gchar *folder_path, *path;
1778 g_return_val_if_fail(folder != NULL, NULL);
1779 g_return_val_if_fail(item != NULL, NULL);
1780 folder_path = imap_folder_get_path(folder);
1782 g_return_val_if_fail(folder_path != NULL, NULL);
1783 if (folder_path[0] == G_DIR_SEPARATOR) {
1785 path = g_strconcat(folder_path, G_DIR_SEPARATOR_S,
1788 path = g_strdup(folder_path);
1791 path = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S,
1792 folder_path, G_DIR_SEPARATOR_S,
1795 path = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S,
1798 g_free(folder_path);
1803 static FolderItem *imap_create_folder(Folder *folder, FolderItem *parent,
1806 gchar *dirpath, *imap_path;
1807 IMAPSession *session;
1808 FolderItem *new_item;
1813 gboolean no_select = FALSE, no_sub = FALSE;
1815 g_return_val_if_fail(folder != NULL, NULL);
1816 g_return_val_if_fail(folder->account != NULL, NULL);
1817 g_return_val_if_fail(parent != NULL, NULL);
1818 g_return_val_if_fail(name != NULL, NULL);
1820 session = imap_session_get(folder);
1826 if (!folder_item_parent(parent) && strcmp(name, "INBOX") == 0) {
1827 dirpath = g_strdup(name);
1828 }else if (parent->path)
1829 dirpath = g_strconcat(parent->path, "/", name, NULL);
1830 else if ((p = strchr(name, '/')) != NULL && *(p + 1) != '\0')
1831 dirpath = g_strdup(name);
1832 else if (folder->account->imap_dir && *folder->account->imap_dir) {
1835 Xstrdup_a(imap_dir, folder->account->imap_dir, {unlock_session();return NULL;});
1836 strtailchomp(imap_dir, '/');
1837 dirpath = g_strconcat(imap_dir, "/", name, NULL);
1839 dirpath = g_strdup(name);
1843 /* keep trailing directory separator to create a folder that contains
1845 imap_path = imap_utf8_to_modified_utf7(dirpath);
1847 strtailchomp(dirpath, '/');
1848 Xstrdup_a(new_name, name, {
1853 separator = imap_get_path_separator(IMAP_FOLDER(folder), imap_path);
1854 imap_path_separator_subst(imap_path, separator);
1855 /* remove trailing / for display */
1856 strtailchomp(new_name, '/');
1858 if (strcmp(dirpath, "INBOX") != 0) {
1860 gboolean exist = FALSE;
1864 argbuf = g_ptr_array_new();
1865 r = imap_threaded_list(folder, "", imap_path, &lep_list);
1866 if (r != MAILIMAP_NO_ERROR) {
1867 log_warning(_("can't create mailbox: LIST failed\n"));
1870 ptr_array_free_strings(argbuf);
1871 g_ptr_array_free(argbuf, TRUE);
1876 if (clist_count(lep_list) > 0)
1878 mailimap_list_result_free(lep_list);
1881 ok = imap_cmd_create(session, imap_path);
1882 if (ok != IMAP_SUCCESS) {
1883 log_warning(_("can't create mailbox\n"));
1889 r = imap_threaded_list(folder, "", imap_path, &lep_list);
1890 if (r == MAILIMAP_NO_ERROR) {
1891 GSList *item_list = imap_list_from_lep(IMAP_FOLDER(folder),
1892 lep_list, dirpath, TRUE);
1894 FolderItem *cur_item = FOLDER_ITEM(item_list->data);
1895 no_select = cur_item->no_select;
1896 no_sub = cur_item->no_sub;
1897 g_slist_free(item_list);
1899 mailimap_list_result_free(lep_list);
1906 /* just get flags */
1907 r = imap_threaded_list(folder, "", "INBOX", &lep_list);
1908 if (r == MAILIMAP_NO_ERROR) {
1909 GSList *item_list = imap_list_from_lep(IMAP_FOLDER(folder),
1910 lep_list, dirpath, TRUE);
1912 FolderItem *cur_item = FOLDER_ITEM(item_list->data);
1913 no_select = cur_item->no_select;
1914 no_sub = cur_item->no_sub;
1915 g_slist_free(item_list);
1917 mailimap_list_result_free(lep_list);
1921 new_item = folder_item_new(folder, new_name, dirpath);
1922 new_item->no_select = no_select;
1923 new_item->no_sub = no_sub;
1924 folder_item_append(parent, new_item);
1928 dirpath = folder_item_get_path(new_item);
1929 if (!is_dir_exist(dirpath))
1930 make_dir_hier(dirpath);
1936 static gint imap_rename_folder(Folder *folder, FolderItem *item,
1941 gchar *real_oldpath;
1942 gchar *real_newpath;
1944 gchar *old_cache_dir;
1945 gchar *new_cache_dir;
1946 IMAPSession *session;
1949 gint exists, recent, unseen;
1950 guint32 uid_validity;
1952 g_return_val_if_fail(folder != NULL, -1);
1953 g_return_val_if_fail(item != NULL, -1);
1954 g_return_val_if_fail(item->path != NULL, -1);
1955 g_return_val_if_fail(name != NULL, -1);
1957 session = imap_session_get(folder);
1963 if (strchr(name, imap_get_path_separator(IMAP_FOLDER(folder), item->path)) != NULL) {
1964 g_warning(_("New folder name must not contain the namespace "
1970 real_oldpath = imap_get_real_path(IMAP_FOLDER(folder), item->path);
1972 g_free(session->mbox);
1973 session->mbox = NULL;
1974 ok = imap_cmd_examine(session, "INBOX",
1975 &exists, &recent, &unseen, &uid_validity, FALSE);
1976 if (ok != IMAP_SUCCESS) {
1977 g_free(real_oldpath);
1982 separator = imap_get_path_separator(IMAP_FOLDER(folder), item->path);
1983 if (strchr(item->path, G_DIR_SEPARATOR)) {
1984 dirpath = g_path_get_dirname(item->path);
1985 newpath = g_strconcat(dirpath, G_DIR_SEPARATOR_S, name, NULL);
1988 newpath = g_strdup(name);
1990 real_newpath = imap_utf8_to_modified_utf7(newpath);
1991 imap_path_separator_subst(real_newpath, separator);
1993 ok = imap_cmd_rename(session, real_oldpath, real_newpath);
1994 if (ok != IMAP_SUCCESS) {
1995 log_warning(_("can't rename mailbox: %s to %s\n"),
1996 real_oldpath, real_newpath);
1997 g_free(real_oldpath);
1999 g_free(real_newpath);
2005 item->name = g_strdup(name);
2007 old_cache_dir = folder_item_get_path(item);
2009 paths[0] = g_strdup(item->path);
2011 g_node_traverse(item->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
2012 imap_rename_folder_func, paths);
2014 if (is_dir_exist(old_cache_dir)) {
2015 new_cache_dir = folder_item_get_path(item);
2016 if (rename(old_cache_dir, new_cache_dir) < 0) {
2017 FILE_OP_ERROR(old_cache_dir, "rename");
2019 g_free(new_cache_dir);
2022 g_free(old_cache_dir);
2025 g_free(real_oldpath);
2026 g_free(real_newpath);
2031 static gint imap_remove_folder_real(Folder *folder, FolderItem *item)
2034 IMAPSession *session;
2038 g_return_val_if_fail(folder != NULL, -1);
2039 g_return_val_if_fail(item != NULL, -1);
2040 g_return_val_if_fail(item->path != NULL, -1);
2042 session = imap_session_get(folder);
2047 path = imap_get_real_path(IMAP_FOLDER(folder), item->path);
2049 ok = imap_cmd_delete(session, path);
2050 if (ok != IMAP_SUCCESS) {
2051 gchar *tmp = g_strdup_printf("%s%c", path,
2052 imap_get_path_separator(IMAP_FOLDER(folder), path));
2055 ok = imap_cmd_delete(session, path);
2058 if (ok != IMAP_SUCCESS) {
2059 log_warning(_("can't delete mailbox\n"));
2066 cache_dir = folder_item_get_path(item);
2067 if (is_dir_exist(cache_dir) && remove_dir_recursive(cache_dir) < 0)
2068 g_warning("can't remove directory '%s'\n", cache_dir);
2070 folder_item_remove(item);
2075 static gint imap_remove_folder(Folder *folder, FolderItem *item)
2079 g_return_val_if_fail(item != NULL, -1);
2080 g_return_val_if_fail(item->folder != NULL, -1);
2081 g_return_val_if_fail(item->node != NULL, -1);
2083 node = item->node->children;
2084 while (node != NULL) {
2086 if (imap_remove_folder(folder, FOLDER_ITEM(node->data)) < 0)
2090 debug_print("IMAP removing %s\n", item->path);
2092 if (imap_remove_all_msg(folder, item) < 0)
2094 return imap_remove_folder_real(folder, item);
2097 typedef struct _uncached_data {
2098 IMAPSession *session;
2100 MsgNumberList *numlist;
2106 static void *imap_get_uncached_messages_thread(void *data)
2108 uncached_data *stuff = (uncached_data *)data;
2109 IMAPSession *session = stuff->session;
2110 FolderItem *item = stuff->item;
2111 MsgNumberList *numlist = stuff->numlist;
2113 GSList *newlist = NULL;
2114 GSList *llast = NULL;
2115 GSList *seq_list, *cur;
2117 debug_print("uncached_messages\n");
2119 if (session == NULL || item == NULL || item->folder == NULL
2120 || FOLDER_CLASS(item->folder) != &imap_class) {
2125 seq_list = imap_get_lep_set_from_numlist(numlist);
2126 debug_print("get msgs info\n");
2127 for (cur = seq_list; cur != NULL; cur = g_slist_next(cur)) {
2128 struct mailimap_set * imapset;
2134 imapset = cur->data;
2136 r = imap_threaded_fetch_env(session->folder,
2137 imapset, &env_list);
2138 if (r != MAILIMAP_NO_ERROR)
2141 session_set_access_time(SESSION(session));
2144 for(i = 0 ; i < carray_count(env_list) ; i ++) {
2145 struct imap_fetch_env_info * info;
2148 info = carray_get(env_list, i);
2149 msginfo = imap_envelope_from_lep(info, item);
2150 if (msginfo == NULL)
2152 msginfo->folder = item;
2154 llast = newlist = g_slist_append(newlist, msginfo);
2156 llast = g_slist_append(llast, msginfo);
2157 llast = llast->next;
2162 imap_fetch_env_free(env_list);
2165 for (cur = seq_list; cur != NULL; cur = g_slist_next(cur)) {
2166 struct mailimap_set * imapset;
2168 imapset = cur->data;
2169 mailimap_set_free(imapset);
2172 session_set_access_time(SESSION(session));
2177 #define MAX_MSG_NUM 50
2179 static GSList *imap_get_uncached_messages(IMAPSession *session,
2181 MsgNumberList *numlist)
2183 GSList *result = NULL;
2185 uncached_data *data = g_new0(uncached_data, 1);
2190 data->total = g_slist_length(numlist);
2191 debug_print("messages list : %i\n", data->total);
2193 while (cur != NULL) {
2194 GSList * partial_result;
2202 while (count < MAX_MSG_NUM) {
2207 if (newlist == NULL)
2208 llast = newlist = g_slist_append(newlist, p);
2210 llast = g_slist_append(llast, p);
2211 llast = llast->next;
2221 data->session = session;
2223 data->numlist = newlist;
2226 if (prefs_common.work_offline &&
2227 !inc_offline_should_override(
2228 _("Claws Mail needs network access in order "
2229 "to access the IMAP server."))) {
2235 (GSList *)imap_get_uncached_messages_thread(data);
2237 statusbar_progress_all(data->cur,data->total, 1);
2239 g_slist_free(newlist);
2241 result = g_slist_concat(result, partial_result);
2245 statusbar_progress_all(0,0,0);
2246 statusbar_pop_all();
2251 static void imap_delete_all_cached_messages(FolderItem *item)
2255 g_return_if_fail(item != NULL);
2256 g_return_if_fail(item->folder != NULL);
2257 g_return_if_fail(FOLDER_CLASS(item->folder) == &imap_class);
2259 debug_print("Deleting all cached messages...\n");
2261 dir = folder_item_get_path(item);
2262 if (is_dir_exist(dir))
2263 remove_all_numbered_files(dir);
2266 debug_print("done.\n");
2269 static IMAPNameSpace *imap_find_namespace_from_list(GList *ns_list,
2272 IMAPNameSpace *namespace = NULL;
2273 gchar *tmp_path, *name;
2275 if (!path) path = "";
2277 for (; ns_list != NULL; ns_list = ns_list->next) {
2278 IMAPNameSpace *tmp_ns = ns_list->data;
2280 Xstrcat_a(tmp_path, path, "/", return namespace);
2281 Xstrdup_a(name, tmp_ns->name, return namespace);
2282 if (tmp_ns->separator && tmp_ns->separator != '/') {
2283 subst_char(tmp_path, tmp_ns->separator, '/');
2284 subst_char(name, tmp_ns->separator, '/');
2286 if (strncmp(tmp_path, name, strlen(name)) == 0)
2293 static IMAPNameSpace *imap_find_namespace(IMAPFolder *folder,
2296 IMAPNameSpace *namespace;
2298 g_return_val_if_fail(folder != NULL, NULL);
2300 namespace = imap_find_namespace_from_list(folder->ns_personal, path);
2301 if (namespace) return namespace;
2302 namespace = imap_find_namespace_from_list(folder->ns_others, path);
2303 if (namespace) return namespace;
2304 namespace = imap_find_namespace_from_list(folder->ns_shared, path);
2305 if (namespace) return namespace;
2310 gchar imap_get_path_separator_for_item(FolderItem *item)
2312 Folder *folder = NULL;
2313 IMAPFolder *imap_folder = NULL;
2316 folder = item->folder;
2321 imap_folder = IMAP_FOLDER(folder);
2326 return imap_get_path_separator(imap_folder, item->path);
2329 static gchar imap_refresh_path_separator(IMAPFolder *folder, const gchar *subfolder)
2331 IMAPSession *session = imap_session_get(FOLDER(folder));
2334 gchar separator = '\0';
2336 g_return_val_if_fail(session != NULL, '/');
2337 r = imap_threaded_list((Folder *)folder, "", subfolder, &lep_list);
2339 if (r != MAILIMAP_NO_ERROR) {
2340 log_warning(_("LIST failed\n"));
2344 if (clist_count(lep_list) > 0) {
2345 clistiter * iter = clist_begin(lep_list);
2346 struct mailimap_mailbox_list * mb;
2347 mb = clist_content(iter);
2349 separator = mb->mb_delimiter;
2350 debug_print("got separator: %c\n", folder->last_seen_separator);
2352 mailimap_list_result_free(lep_list);
2356 static gchar imap_get_path_separator(IMAPFolder *folder, const gchar *path)
2358 IMAPNameSpace *namespace;
2359 gchar separator = '/';
2360 IMAPSession *session = imap_session_get(FOLDER(folder));
2361 g_return_val_if_fail(session != NULL, '/');
2363 if (folder->last_seen_separator == 0) {
2364 folder->last_seen_separator = imap_refresh_path_separator(folder, "");
2367 if (folder->last_seen_separator == 0) {
2368 folder->last_seen_separator = imap_refresh_path_separator(folder, "INBOX");
2371 if (folder->last_seen_separator != 0) {
2372 debug_print("using separator: %c\n", folder->last_seen_separator);
2373 return folder->last_seen_separator;
2379 static gchar *imap_get_real_path(IMAPFolder *folder, const gchar *path)
2384 g_return_val_if_fail(folder != NULL, NULL);
2385 g_return_val_if_fail(path != NULL, NULL);
2387 real_path = imap_utf8_to_modified_utf7(path);
2388 separator = imap_get_path_separator(folder, path);
2389 imap_path_separator_subst(real_path, separator);
2394 static gint imap_set_message_flags(IMAPSession *session,
2395 MsgNumberList *numlist,
2403 seq_list = imap_get_lep_set_from_numlist(numlist);
2405 for(cur = seq_list ; cur != NULL ; cur = g_slist_next(cur)) {
2406 struct mailimap_set * imapset;
2408 imapset = cur->data;
2410 ok = imap_cmd_store(session, imapset,
2414 imap_lep_set_free(seq_list);
2416 return IMAP_SUCCESS;
2419 typedef struct _select_data {
2420 IMAPSession *session;
2425 guint32 *uid_validity;
2429 static gint imap_select(IMAPSession *session, IMAPFolder *folder,
2431 gint *exists, gint *recent, gint *unseen,
2432 guint32 *uid_validity, gboolean block)
2436 gint exists_, recent_, unseen_;
2437 guint32 uid_validity_;
2439 if (!exists && !recent && !unseen && !uid_validity) {
2440 if (session->mbox && strcmp(session->mbox, path) == 0)
2441 return IMAP_SUCCESS;
2450 uid_validity = &uid_validity_;
2452 g_free(session->mbox);
2453 session->mbox = NULL;
2455 real_path = imap_get_real_path(folder, path);
2457 ok = imap_cmd_select(session, real_path,
2458 exists, recent, unseen, uid_validity, block);
2459 if (ok != IMAP_SUCCESS)
2460 log_warning(_("can't select folder: %s\n"), real_path);
2462 session->mbox = g_strdup(path);
2463 session->folder_content_changed = FALSE;
2470 static gint imap_status(IMAPSession *session, IMAPFolder *folder,
2471 const gchar *path, IMAPFolderItem *item,
2473 guint32 *uid_next, guint32 *uid_validity,
2474 gint *unseen, gboolean block)
2478 struct mailimap_mailbox_data_status * data_status;
2483 real_path = imap_get_real_path(folder, path);
2497 r = imap_threaded_status(FOLDER(folder), real_path,
2498 &data_status, mask);
2501 if (r != MAILIMAP_NO_ERROR) {
2502 debug_print("status err %d\n", r);
2506 if (data_status->st_info_list == NULL) {
2507 mailimap_mailbox_data_status_free(data_status);
2508 debug_print("status->st_info_list == NULL\n");
2513 for(iter = clist_begin(data_status->st_info_list) ; iter != NULL ;
2514 iter = clist_next(iter)) {
2515 struct mailimap_status_info * info;
2517 info = clist_content(iter);
2518 switch (info->st_att) {
2519 case MAILIMAP_STATUS_ATT_MESSAGES:
2520 * messages = info->st_value;
2521 got_values |= 1 << 0;
2524 case MAILIMAP_STATUS_ATT_UIDNEXT:
2525 * uid_next = info->st_value;
2526 got_values |= 1 << 2;
2529 case MAILIMAP_STATUS_ATT_UIDVALIDITY:
2530 * uid_validity = info->st_value;
2531 got_values |= 1 << 3;
2534 case MAILIMAP_STATUS_ATT_UNSEEN:
2535 * unseen = info->st_value;
2536 got_values |= 1 << 4;
2540 mailimap_mailbox_data_status_free(data_status);
2542 if (got_values != mask) {
2543 debug_print("status: incomplete values received (%d)\n", got_values);
2546 return IMAP_SUCCESS;
2549 static void imap_free_capabilities(IMAPSession *session)
2551 slist_free_strings(session->capability);
2552 g_slist_free(session->capability);
2553 session->capability = NULL;
2556 /* low-level IMAP4rev1 commands */
2558 static gint imap_cmd_login(IMAPSession *session,
2559 const gchar *user, const gchar *pass,
2565 if (!strcmp(type, "LOGIN") && imap_has_capability(session, "LOGINDISABLED")) {
2566 gint ok = IMAP_ERROR;
2567 if (imap_has_capability(session, "STARTTLS")) {
2569 log_warning(_("Server requires TLS to log in.\n"));
2570 ok = imap_cmd_starttls(session);
2571 if (ok != IMAP_SUCCESS) {
2572 log_warning(_("Can't start TLS session.\n"));
2576 imap_free_capabilities(session);
2577 if (imap_get_capabilities(session) != MAILIMAP_NO_ERROR) {
2578 log_warning(_("Can't refresh capabilities.\n"));
2583 log_error(_("Connection to %s failed: "
2584 "server requires TLS, but Claws Mail "
2585 "has been compiled without OpenSSL "
2587 SESSION(session)->server);
2591 log_error(_("Server logins are disabled.\n"));
2596 log_print("IMAP4> Logging %s to %s using %s\n",
2598 SESSION(session)->server,
2600 r = imap_threaded_login(session->folder, user, pass, type);
2601 if (r != MAILIMAP_NO_ERROR) {
2602 log_print("IMAP4< Error logging in to %s\n",
2603 SESSION(session)->server);
2606 log_print("IMAP4< Login to %s successful\n",
2607 SESSION(session)->server);
2613 static gint imap_cmd_noop(IMAPSession *session)
2616 unsigned int exists;
2618 r = imap_threaded_noop(session->folder, &exists);
2619 if (r != MAILIMAP_NO_ERROR) {
2620 debug_print("noop err %d\n", r);
2623 session->exists = exists;
2624 session_set_access_time(SESSION(session));
2626 return IMAP_SUCCESS;
2630 static gint imap_cmd_starttls(IMAPSession *session)
2634 r = imap_threaded_starttls(session->folder,
2635 SESSION(session)->server, SESSION(session)->port);
2636 if (r != MAILIMAP_NO_ERROR) {
2637 debug_print("starttls err %d\n", r);
2640 return IMAP_SUCCESS;
2644 static gint imap_cmd_select(IMAPSession *session, const gchar *folder,
2645 gint *exists, gint *recent, gint *unseen,
2646 guint32 *uid_validity, gboolean block)
2650 r = imap_threaded_select(session->folder, folder,
2651 exists, recent, unseen, uid_validity);
2652 if (r != MAILIMAP_NO_ERROR) {
2653 debug_print("select err %d\n", r);
2656 return IMAP_SUCCESS;
2659 static gint imap_cmd_examine(IMAPSession *session, const gchar *folder,
2660 gint *exists, gint *recent, gint *unseen,
2661 guint32 *uid_validity, gboolean block)
2665 r = imap_threaded_examine(session->folder, folder,
2666 exists, recent, unseen, uid_validity);
2667 if (r != MAILIMAP_NO_ERROR) {
2668 debug_print("examine err %d\n", r);
2672 return IMAP_SUCCESS;
2675 static gint imap_cmd_create(IMAPSession *session, const gchar *folder)
2679 r = imap_threaded_create(session->folder, folder);
2680 if (r != MAILIMAP_NO_ERROR) {
2685 return IMAP_SUCCESS;
2688 static gint imap_cmd_rename(IMAPSession *session, const gchar *old_folder,
2689 const gchar *new_folder)
2693 r = imap_threaded_rename(session->folder, old_folder,
2695 if (r != MAILIMAP_NO_ERROR) {
2700 return IMAP_SUCCESS;
2703 static gint imap_cmd_delete(IMAPSession *session, const gchar *folder)
2708 r = imap_threaded_delete(session->folder, folder);
2709 if (r != MAILIMAP_NO_ERROR) {
2714 return IMAP_SUCCESS;
2717 typedef struct _fetch_data {
2718 IMAPSession *session;
2720 const gchar *filename;
2726 static void *imap_cmd_fetch_thread(void *data)
2728 fetch_data *stuff = (fetch_data *)data;
2729 IMAPSession *session = stuff->session;
2730 guint32 uid = stuff->uid;
2731 const gchar *filename = stuff->filename;
2735 r = imap_threaded_fetch_content(session->folder,
2739 r = imap_threaded_fetch_content(session->folder,
2742 if (r != MAILIMAP_NO_ERROR) {
2743 debug_print("fetch err %d\n", r);
2744 return GINT_TO_POINTER(IMAP_ERROR);
2746 return GINT_TO_POINTER(IMAP_SUCCESS);
2749 static gint imap_cmd_fetch(IMAPSession *session, guint32 uid,
2750 const gchar *filename, gboolean headers,
2753 fetch_data *data = g_new0(fetch_data, 1);
2756 data->session = session;
2758 data->filename = filename;
2759 data->headers = headers;
2762 if (prefs_common.work_offline &&
2763 !inc_offline_should_override(
2764 _("Claws Mail needs network access in order "
2765 "to access the IMAP server."))) {
2769 statusbar_print_all(_("Fetching message..."));
2770 result = GPOINTER_TO_INT(imap_cmd_fetch_thread(data));
2771 statusbar_pop_all();
2777 static gint imap_cmd_append(IMAPSession *session, const gchar *destfolder,
2778 const gchar *file, IMAPFlags flags,
2781 struct mailimap_flag_list * flag_list;
2784 g_return_val_if_fail(file != NULL, IMAP_ERROR);
2786 flag_list = imap_flag_to_lep(flags);
2787 r = imap_threaded_append(session->folder, destfolder,
2788 file, flag_list, (int *)new_uid);
2789 mailimap_flag_list_free(flag_list);
2791 if (r != MAILIMAP_NO_ERROR) {
2792 debug_print("append err %d\n", r);
2795 return IMAP_SUCCESS;
2798 static gint imap_cmd_copy(IMAPSession *session, struct mailimap_set * set,
2799 const gchar *destfolder, GRelation *uid_mapping,
2800 struct mailimap_set **source, struct mailimap_set **dest)
2804 g_return_val_if_fail(session != NULL, IMAP_ERROR);
2805 g_return_val_if_fail(set != NULL, IMAP_ERROR);
2806 g_return_val_if_fail(destfolder != NULL, IMAP_ERROR);
2808 r = imap_threaded_copy(session->folder, set, destfolder, source, dest);
2809 if (r != MAILIMAP_NO_ERROR) {
2814 return IMAP_SUCCESS;
2817 static gint imap_cmd_store(IMAPSession *session, struct mailimap_set * set,
2818 IMAPFlags flags, int do_add)
2821 struct mailimap_flag_list * flag_list;
2822 struct mailimap_store_att_flags * store_att_flags;
2824 flag_list = imap_flag_to_lep(flags);
2828 mailimap_store_att_flags_new_add_flags_silent(flag_list);
2831 mailimap_store_att_flags_new_remove_flags_silent(flag_list);
2833 r = imap_threaded_store(session->folder, set, store_att_flags);
2834 mailimap_store_att_flags_free(store_att_flags);
2835 if (r != MAILIMAP_NO_ERROR) {
2840 return IMAP_SUCCESS;
2843 static gint imap_cmd_expunge(IMAPSession *session)
2847 if (prefs_common.work_offline &&
2848 !inc_offline_should_override(
2849 _("Claws Mail needs network access in order "
2850 "to access the IMAP server."))) {
2854 r = imap_threaded_expunge(session->folder);
2855 if (r != MAILIMAP_NO_ERROR) {
2860 return IMAP_SUCCESS;
2863 static void imap_path_separator_subst(gchar *str, gchar separator)
2866 gboolean in_escape = FALSE;
2868 if (!separator || separator == '/') return;
2870 for (p = str; *p != '\0'; p++) {
2871 if (*p == '/' && !in_escape)
2873 else if (*p == '&' && *(p + 1) != '-' && !in_escape)
2875 else if (*p == '-' && in_escape)
2880 static gchar *imap_modified_utf7_to_utf8(const gchar *mutf7_str)
2882 static iconv_t cd = (iconv_t)-1;
2883 static gboolean iconv_ok = TRUE;
2886 size_t norm_utf7_len;
2888 gchar *to_str, *to_p;
2890 gboolean in_escape = FALSE;
2892 if (!iconv_ok) return g_strdup(mutf7_str);
2894 if (cd == (iconv_t)-1) {
2895 cd = iconv_open(CS_INTERNAL, CS_UTF_7);
2896 if (cd == (iconv_t)-1) {
2897 g_warning("iconv cannot convert UTF-7 to %s\n",
2900 return g_strdup(mutf7_str);
2904 /* modified UTF-7 to normal UTF-7 conversion */
2905 norm_utf7 = g_string_new(NULL);
2907 for (p = mutf7_str; *p != '\0'; p++) {
2908 /* replace: '&' -> '+',
2910 escaped ',' -> '/' */
2911 if (!in_escape && *p == '&') {
2912 if (*(p + 1) != '-') {
2913 g_string_append_c(norm_utf7, '+');
2916 g_string_append_c(norm_utf7, '&');
2919 } else if (in_escape && *p == ',') {
2920 g_string_append_c(norm_utf7, '/');
2921 } else if (in_escape && *p == '-') {
2922 g_string_append_c(norm_utf7, '-');
2925 g_string_append_c(norm_utf7, *p);
2929 norm_utf7_p = norm_utf7->str;
2930 norm_utf7_len = norm_utf7->len;
2931 to_len = strlen(mutf7_str) * 5;
2932 to_p = to_str = g_malloc(to_len + 1);
2934 if (iconv(cd, (ICONV_CONST gchar **)&norm_utf7_p, &norm_utf7_len,
2935 &to_p, &to_len) == -1) {
2936 g_warning(_("iconv cannot convert UTF-7 to %s\n"),
2937 conv_get_locale_charset_str());
2938 g_string_free(norm_utf7, TRUE);
2940 return g_strdup(mutf7_str);
2943 /* second iconv() call for flushing */
2944 iconv(cd, NULL, NULL, &to_p, &to_len);
2945 g_string_free(norm_utf7, TRUE);
2951 static gchar *imap_utf8_to_modified_utf7(const gchar *from)
2953 static iconv_t cd = (iconv_t)-1;
2954 static gboolean iconv_ok = TRUE;
2955 gchar *norm_utf7, *norm_utf7_p;
2956 size_t from_len, norm_utf7_len;
2958 gchar *from_tmp, *to, *p;
2959 gboolean in_escape = FALSE;
2961 if (!iconv_ok) return g_strdup(from);
2963 if (cd == (iconv_t)-1) {
2964 cd = iconv_open(CS_UTF_7, CS_INTERNAL);
2965 if (cd == (iconv_t)-1) {
2966 g_warning(_("iconv cannot convert %s to UTF-7\n"),
2969 return g_strdup(from);
2973 /* UTF-8 to normal UTF-7 conversion */
2974 Xstrdup_a(from_tmp, from, return g_strdup(from));
2975 from_len = strlen(from);
2976 norm_utf7_len = from_len * 5;
2977 Xalloca(norm_utf7, norm_utf7_len + 1, return g_strdup(from));
2978 norm_utf7_p = norm_utf7;
2980 #define IS_PRINT(ch) (isprint(ch) && IS_ASCII(ch))
2982 while (from_len > 0) {
2983 if (*from_tmp == '+') {
2984 *norm_utf7_p++ = '+';
2985 *norm_utf7_p++ = '-';
2989 } else if (IS_PRINT(*(guchar *)from_tmp)) {
2990 /* printable ascii char */
2991 *norm_utf7_p = *from_tmp;
2997 size_t conv_len = 0;
2999 /* unprintable char: convert to UTF-7 */
3001 while (!IS_PRINT(*(guchar *)p) && conv_len < from_len) {
3002 conv_len += g_utf8_skip[*(guchar *)p];
3003 p += g_utf8_skip[*(guchar *)p];
3006 from_len -= conv_len;
3007 if (iconv(cd, (ICONV_CONST gchar **)&from_tmp,
3009 &norm_utf7_p, &norm_utf7_len) == -1) {
3010 g_warning(_("iconv cannot convert UTF-8 to UTF-7\n"));
3011 return g_strdup(from);
3014 /* second iconv() call for flushing */
3015 iconv(cd, NULL, NULL, &norm_utf7_p, &norm_utf7_len);
3021 *norm_utf7_p = '\0';
3022 to_str = g_string_new(NULL);
3023 for (p = norm_utf7; p < norm_utf7_p; p++) {
3024 /* replace: '&' -> "&-",
3027 BASE64 '/' -> ',' */
3028 if (!in_escape && *p == '&') {
3029 g_string_append(to_str, "&-");
3030 } else if (!in_escape && *p == '+') {
3031 if (*(p + 1) == '-') {
3032 g_string_append_c(to_str, '+');
3035 g_string_append_c(to_str, '&');
3038 } else if (in_escape && *p == '/') {
3039 g_string_append_c(to_str, ',');
3040 } else if (in_escape && *p == '-') {
3041 g_string_append_c(to_str, '-');
3044 g_string_append_c(to_str, *p);
3050 g_string_append_c(to_str, '-');
3054 g_string_free(to_str, FALSE);
3059 static gboolean imap_rename_folder_func(GNode *node, gpointer data)
3061 FolderItem *item = node->data;
3062 gchar **paths = data;
3063 const gchar *oldpath = paths[0];
3064 const gchar *newpath = paths[1];
3066 gchar *new_itempath;
3069 oldpathlen = strlen(oldpath);
3070 if (strncmp(oldpath, item->path, oldpathlen) != 0) {
3071 g_warning("path doesn't match: %s, %s\n", oldpath, item->path);
3075 base = item->path + oldpathlen;
3076 while (*base == G_DIR_SEPARATOR) base++;
3078 new_itempath = g_strdup(newpath);
3080 new_itempath = g_strconcat(newpath, G_DIR_SEPARATOR_S, base,
3083 item->path = new_itempath;
3088 typedef struct _get_list_uid_data {
3090 IMAPSession *session;
3091 IMAPFolderItem *item;
3092 GSList **msgnum_list;
3094 } get_list_uid_data;
3096 static void *get_list_of_uids_thread(void *data)
3098 get_list_uid_data *stuff = (get_list_uid_data *)data;
3099 Folder *folder = stuff->folder;
3100 IMAPFolderItem *item = stuff->item;
3101 GSList **msgnum_list = stuff->msgnum_list;
3102 gint ok, nummsgs = 0, lastuid_old;
3103 IMAPSession *session;
3104 GSList *uidlist, *elem;
3105 clist * lep_uidlist;
3108 session = stuff->session;
3109 if (session == NULL) {
3111 return GINT_TO_POINTER(-1);
3113 /* no session locking here, it's already locked by caller */
3114 ok = imap_select(session, IMAP_FOLDER(folder), item->item.path,
3115 NULL, NULL, NULL, NULL, TRUE);
3116 if (ok != IMAP_SUCCESS) {
3118 return GINT_TO_POINTER(-1);
3123 r = imap_threaded_search(folder, IMAP_SEARCH_TYPE_SIMPLE, NULL,
3126 if (r == MAILIMAP_NO_ERROR) {
3127 GSList * fetchuid_list;
3130 imap_uid_list_from_lep(lep_uidlist);
3131 mailimap_search_result_free(lep_uidlist);
3133 uidlist = g_slist_concat(fetchuid_list, uidlist);
3136 GSList * fetchuid_list;
3137 carray * lep_uidtab;
3139 r = imap_threaded_fetch_uid(folder, item->lastuid + 1,
3141 if (r == MAILIMAP_NO_ERROR) {
3143 imap_uid_list_from_lep_tab(lep_uidtab);
3144 imap_fetch_uid_list_free(lep_uidtab);
3145 uidlist = g_slist_concat(fetchuid_list, uidlist);
3149 lastuid_old = item->lastuid;
3150 *msgnum_list = g_slist_copy(item->uid_list);
3151 nummsgs = g_slist_length(*msgnum_list);
3152 debug_print("Got %d uids from cache\n", g_slist_length(item->uid_list));
3154 for (elem = uidlist; elem != NULL; elem = g_slist_next(elem)) {
3157 msgnum = GPOINTER_TO_INT(elem->data);
3158 if (msgnum > lastuid_old) {
3159 *msgnum_list = g_slist_prepend(*msgnum_list, GINT_TO_POINTER(msgnum));
3160 item->uid_list = g_slist_prepend(item->uid_list, GINT_TO_POINTER(msgnum));
3163 if(msgnum > item->lastuid)
3164 item->lastuid = msgnum;
3167 g_slist_free(uidlist);
3169 return GINT_TO_POINTER(nummsgs);
3172 static gint get_list_of_uids(IMAPSession *session, Folder *folder, IMAPFolderItem *item, GSList **msgnum_list)
3175 get_list_uid_data *data = g_new0(get_list_uid_data, 1);
3177 data->folder = folder;
3179 data->msgnum_list = msgnum_list;
3180 data->session = session;
3181 if (prefs_common.work_offline &&
3182 !inc_offline_should_override(
3183 _("Claws Mail needs network access in order "
3184 "to access the IMAP server."))) {
3189 result = GPOINTER_TO_INT(get_list_of_uids_thread(data));
3195 gint imap_get_num_list(Folder *folder, FolderItem *_item, GSList **msgnum_list, gboolean *old_uids_valid)
3197 IMAPFolderItem *item = (IMAPFolderItem *)_item;
3198 IMAPSession *session;
3199 gint ok, nummsgs = 0, exists;
3200 guint32 uid_next = 0, uid_val = 0;
3201 GSList *uidlist = NULL;
3203 gboolean selected_folder;
3204 debug_print("get_num_list\n");
3206 g_return_val_if_fail(folder != NULL, -1);
3207 g_return_val_if_fail(item != NULL, -1);
3208 g_return_val_if_fail(item->item.path != NULL, -1);
3209 g_return_val_if_fail(FOLDER_CLASS(folder) == &imap_class, -1);
3210 g_return_val_if_fail(folder->account != NULL, -1);
3212 session = imap_session_get(folder);
3213 g_return_val_if_fail(session != NULL, -1);
3216 if (FOLDER_ITEM(item)->path)
3217 statusbar_print_all(_("Scanning folder %s%c%s ..."),
3218 FOLDER_ITEM(item)->folder->name,
3220 FOLDER_ITEM(item)->path);
3222 statusbar_print_all(_("Scanning folder %s ..."),
3223 FOLDER_ITEM(item)->folder->name);
3225 selected_folder = (session->mbox != NULL) &&
3226 (!strcmp(session->mbox, item->item.path));
3227 if (selected_folder && time(NULL) - item->use_cache < 2) {
3228 ok = imap_cmd_noop(session);
3229 if (ok != IMAP_SUCCESS) {
3230 debug_print("disconnected!\n");
3231 session = imap_reconnect_if_possible(folder, session);
3232 if (session == NULL) {
3233 statusbar_pop_all();
3238 exists = session->exists;
3240 uid_next = item->c_uid_next;
3241 uid_val = item->c_uid_validity;
3242 *old_uids_valid = TRUE;
3244 if (item->use_cache && time(NULL) - item->use_cache < 2) {
3245 exists = item->c_messages;
3246 uid_next = item->c_uid_next;
3247 uid_val = item->c_uid_validity;
3249 debug_print("using cache %d %d %d\n", exists, uid_next, uid_val);
3251 ok = imap_status(session, IMAP_FOLDER(folder), item->item.path, item,
3252 &exists, &uid_next, &uid_val, NULL, FALSE);
3254 item->item.last_num = uid_next - 1;
3256 item->use_cache = (time_t)0;
3257 if (ok != IMAP_SUCCESS) {
3258 statusbar_pop_all();
3262 if(item->item.mtime == uid_val)
3263 *old_uids_valid = TRUE;
3265 *old_uids_valid = FALSE;
3267 debug_print("Freeing imap uid cache (%d != %d)\n",
3268 (int)item->item.mtime, uid_val);
3270 g_slist_free(item->uid_list);
3271 item->uid_list = NULL;
3273 item->item.mtime = uid_val;
3275 imap_delete_all_cached_messages((FolderItem *)item);
3279 /* If old uid_next matches new uid_next we can be sure no message
3280 was added to the folder */
3281 debug_print("uid_next is %d and item->uid_next %d \n",
3282 uid_next, item->uid_next);
3283 if (uid_next == item->uid_next) {
3284 nummsgs = g_slist_length(item->uid_list);
3286 /* If number of messages is still the same we
3287 know our caches message numbers are still valid,
3288 otherwise if the number of messages has decrease
3289 we discard our cache to start a new scan to find
3290 out which numbers have been removed */
3291 if (exists == nummsgs) {
3292 debug_print("exists == nummsgs\n");
3293 *msgnum_list = g_slist_copy(item->uid_list);
3294 statusbar_pop_all();
3297 } else if (exists < nummsgs) {
3298 debug_print("Freeing imap uid cache");
3300 g_slist_free(item->uid_list);
3301 item->uid_list = NULL;
3306 *msgnum_list = NULL;
3307 statusbar_pop_all();
3312 nummsgs = get_list_of_uids(session, folder, item, &uidlist);
3315 statusbar_pop_all();
3320 if (nummsgs != exists) {
3321 /* Cache contains more messages then folder, we have cached
3322 an old UID of a message that was removed and new messages
3323 have been added too, otherwise the uid_next check would
3325 debug_print("Freeing imap uid cache");
3327 g_slist_free(item->uid_list);
3328 item->uid_list = NULL;
3330 g_slist_free(*msgnum_list);
3332 nummsgs = get_list_of_uids(session, folder, item, &uidlist);
3335 *msgnum_list = uidlist;
3337 dir = folder_item_get_path((FolderItem *)item);
3338 debug_print("removing old messages from %s\n", dir);
3339 remove_numbered_files_not_in_list(dir, *msgnum_list);
3342 item->uid_next = uid_next;
3344 debug_print("get_num_list - ok - %i\n", nummsgs);
3345 statusbar_pop_all();
3350 static MsgInfo *imap_parse_msg(const gchar *file, FolderItem *item)
3355 flags.perm_flags = MSG_NEW|MSG_UNREAD;
3356 flags.tmp_flags = 0;
3358 g_return_val_if_fail(item != NULL, NULL);
3359 g_return_val_if_fail(file != NULL, NULL);
3361 if (folder_has_parent_of_type(item, F_QUEUE)) {
3362 MSG_SET_TMP_FLAGS(flags, MSG_QUEUED);
3363 } else if (folder_has_parent_of_type(item, F_DRAFT)) {
3364 MSG_SET_TMP_FLAGS(flags, MSG_DRAFT);
3367 msginfo = procheader_parse_file(file, flags, FALSE, FALSE);
3368 if (!msginfo) return NULL;
3370 msginfo->plaintext_file = g_strdup(file);
3371 msginfo->folder = item;
3376 GSList *imap_get_msginfos(Folder *folder, FolderItem *item,
3377 GSList *msgnum_list)
3379 IMAPSession *session;
3380 MsgInfoList *ret = NULL;
3383 debug_print("get_msginfos\n");
3385 g_return_val_if_fail(folder != NULL, NULL);
3386 g_return_val_if_fail(item != NULL, NULL);
3387 g_return_val_if_fail(msgnum_list != NULL, NULL);
3389 session = imap_session_get(folder);
3390 g_return_val_if_fail(session != NULL, NULL);
3392 debug_print("IMAP getting msginfos\n");
3393 ok = imap_select(session, IMAP_FOLDER(folder), item->path,
3394 NULL, NULL, NULL, NULL, FALSE);
3395 if (ok != IMAP_SUCCESS) {
3399 if (!(folder_has_parent_of_type(item, F_DRAFT) ||
3400 folder_has_parent_of_type(item, F_QUEUE))) {
3401 ret = g_slist_concat(ret,
3402 imap_get_uncached_messages(session, item,
3405 MsgNumberList *sorted_list, *elem, *llast = NULL;
3406 gint startnum, lastnum;
3408 sorted_list = g_slist_sort(g_slist_copy(msgnum_list), g_int_compare);
3410 startnum = lastnum = GPOINTER_TO_INT(sorted_list->data);
3412 llast = g_slist_last(ret);
3413 for (elem = sorted_list;; elem = g_slist_next(elem)) {
3417 num = GPOINTER_TO_INT(elem->data);
3419 if (num > lastnum + 1 || elem == NULL) {
3421 for (i = startnum; i <= lastnum; ++i) {
3424 file = imap_fetch_msg(folder, item, i);
3426 MsgInfo *msginfo = imap_parse_msg(file, item);
3427 if (msginfo != NULL) {
3428 msginfo->msgnum = i;
3430 llast = ret = g_slist_append(ret, msginfo);
3432 llast = g_slist_append(llast, msginfo);
3433 llast = llast->next;
3438 session_set_access_time(SESSION(session));
3449 g_slist_free(sorted_list);
3455 MsgInfo *imap_get_msginfo(Folder *folder, FolderItem *item, gint uid)
3457 MsgInfo *msginfo = NULL;
3458 MsgInfoList *msginfolist;
3459 MsgNumberList numlist;
3461 numlist.next = NULL;
3462 numlist.data = GINT_TO_POINTER(uid);
3464 msginfolist = imap_get_msginfos(folder, item, &numlist);
3465 if (msginfolist != NULL) {
3466 msginfo = msginfolist->data;
3467 g_slist_free(msginfolist);
3473 gboolean imap_scan_required(Folder *folder, FolderItem *_item)
3475 IMAPSession *session;
3476 IMAPFolderItem *item = (IMAPFolderItem *)_item;
3477 gint ok, exists = 0, unseen = 0;
3478 guint32 uid_next = 0, uid_val = 0;
3479 gboolean selected_folder;
3481 g_return_val_if_fail(folder != NULL, FALSE);
3482 g_return_val_if_fail(item != NULL, FALSE);
3483 g_return_val_if_fail(item->item.folder != NULL, FALSE);
3484 g_return_val_if_fail(FOLDER_CLASS(item->item.folder) == &imap_class, FALSE);
3486 if (item->item.path == NULL)
3489 session = imap_session_get(folder);
3490 g_return_val_if_fail(session != NULL, FALSE);
3492 selected_folder = (session->mbox != NULL) &&
3493 (!strcmp(session->mbox, item->item.path));
3494 if (selected_folder && time(NULL) - item->use_cache < 2) {
3495 ok = imap_cmd_noop(session);
3496 if (ok != IMAP_SUCCESS) {
3497 debug_print("disconnected!\n");
3498 session = imap_reconnect_if_possible(folder, session);
3499 if (session == NULL)
3504 if (session->folder_content_changed
3505 || session->exists != item->item.total_msgs) {
3510 ok = imap_status(session, IMAP_FOLDER(folder), item->item.path, IMAP_FOLDER_ITEM(item),
3511 &exists, &uid_next, &uid_val, &unseen, FALSE);
3512 if (ok != IMAP_SUCCESS) {
3517 item->use_cache = time(NULL);
3518 item->c_messages = exists;
3519 item->c_uid_next = uid_next;
3520 item->c_uid_validity = uid_val;
3521 item->c_unseen = unseen;
3522 item->item.last_num = uid_next - 1;
3523 debug_print("uidnext %d, item->uid_next %d, exists %d, item->item.total_msgs %d\n",
3524 uid_next, item->uid_next, exists, item->item.total_msgs);
3525 if ((uid_next != item->uid_next) || (exists != item->item.total_msgs)
3526 || unseen != item->item.unread_msgs || uid_val != item->item.mtime) {
3535 void imap_change_flags(Folder *folder, FolderItem *item, MsgInfo *msginfo, MsgPermFlags newflags)
3537 IMAPSession *session;
3538 IMAPFlags flags_set = 0, flags_unset = 0;
3539 gint ok = IMAP_SUCCESS;
3540 MsgNumberList numlist;
3541 hashtable_data *ht_data = NULL;
3543 g_return_if_fail(folder != NULL);
3544 g_return_if_fail(folder->klass == &imap_class);
3545 g_return_if_fail(item != NULL);
3546 g_return_if_fail(item->folder == folder);
3547 g_return_if_fail(msginfo != NULL);
3548 g_return_if_fail(msginfo->folder == item);
3550 session = imap_session_get(folder);
3555 if (!MSG_IS_MARKED(msginfo->flags) && (newflags & MSG_MARKED))
3556 flags_set |= IMAP_FLAG_FLAGGED;
3557 if ( MSG_IS_MARKED(msginfo->flags) && !(newflags & MSG_MARKED))
3558 flags_unset |= IMAP_FLAG_FLAGGED;
3560 if (!MSG_IS_UNREAD(msginfo->flags) && (newflags & MSG_UNREAD))
3561 flags_unset |= IMAP_FLAG_SEEN;
3562 if ( MSG_IS_UNREAD(msginfo->flags) && !(newflags & MSG_UNREAD))
3563 flags_set |= IMAP_FLAG_SEEN;
3565 if (!MSG_IS_REPLIED(msginfo->flags) && (newflags & MSG_REPLIED))
3566 flags_set |= IMAP_FLAG_ANSWERED;
3567 if ( MSG_IS_REPLIED(msginfo->flags) && !(newflags & MSG_REPLIED))
3568 flags_unset |= IMAP_FLAG_ANSWERED;
3570 if (!MSG_IS_DELETED(msginfo->flags) && (newflags & MSG_DELETED))
3571 flags_set |= IMAP_FLAG_DELETED;
3572 if ( MSG_IS_DELETED(msginfo->flags) && !(newflags & MSG_DELETED))
3573 flags_unset |= IMAP_FLAG_DELETED;
3575 if (!flags_set && !flags_unset) {
3576 /* the changed flags were not translatable to IMAP-speak.
3577 * like MSG_POSTFILTERED, so just apply. */
3578 msginfo->flags.perm_flags = newflags;
3583 if ((ok = imap_select(session, IMAP_FOLDER(folder), msginfo->folder->path,
3584 NULL, NULL, NULL, NULL, FALSE)) != IMAP_SUCCESS) {
3588 numlist.next = NULL;
3589 numlist.data = GINT_TO_POINTER(msginfo->msgnum);
3591 if (IMAP_FOLDER_ITEM(item)->batching) {
3592 /* instead of performing an UID STORE command for each message change,
3593 * as a lot of them can change "together", we just fill in hashtables
3594 * and defer the treatment so that we're able to send only one
3597 debug_print("IMAP batch mode on, deferring flags change\n");
3599 ht_data = g_hash_table_lookup(IMAP_FOLDER_ITEM(item)->flags_set_table,
3600 GINT_TO_POINTER(flags_set));
3601 if (ht_data == NULL) {
3602 ht_data = g_new0(hashtable_data, 1);
3603 ht_data->session = session;
3604 ht_data->item = IMAP_FOLDER_ITEM(item);
3605 g_hash_table_insert(IMAP_FOLDER_ITEM(item)->flags_set_table,
3606 GINT_TO_POINTER(flags_set), ht_data);
3608 if (!g_slist_find(ht_data->msglist, GINT_TO_POINTER(msginfo->msgnum)))
3609 ht_data->msglist = g_slist_prepend(ht_data->msglist, GINT_TO_POINTER(msginfo->msgnum));
3612 ht_data = g_hash_table_lookup(IMAP_FOLDER_ITEM(item)->flags_unset_table,
3613 GINT_TO_POINTER(flags_unset));
3614 if (ht_data == NULL) {
3615 ht_data = g_new0(hashtable_data, 1);
3616 ht_data->session = session;
3617 ht_data->item = IMAP_FOLDER_ITEM(item);
3618 g_hash_table_insert(IMAP_FOLDER_ITEM(item)->flags_unset_table,
3619 GINT_TO_POINTER(flags_unset), ht_data);
3621 if (!g_slist_find(ht_data->msglist, GINT_TO_POINTER(msginfo->msgnum)))
3622 ht_data->msglist = g_slist_prepend(ht_data->msglist,
3623 GINT_TO_POINTER(msginfo->msgnum));
3626 debug_print("IMAP changing flags\n");
3628 ok = imap_set_message_flags(session, &numlist, flags_set, TRUE);
3629 if (ok != IMAP_SUCCESS) {
3636 ok = imap_set_message_flags(session, &numlist, flags_unset, FALSE);
3637 if (ok != IMAP_SUCCESS) {
3643 msginfo->flags.perm_flags = newflags;
3648 static gint imap_remove_msg(Folder *folder, FolderItem *item, gint uid)
3651 IMAPSession *session;
3653 MsgNumberList numlist;
3655 g_return_val_if_fail(folder != NULL, -1);
3656 g_return_val_if_fail(FOLDER_CLASS(folder) == &imap_class, -1);
3657 g_return_val_if_fail(item != NULL, -1);
3659 session = imap_session_get(folder);
3660 if (!session) return -1;
3662 ok = imap_select(session, IMAP_FOLDER(folder), item->path,
3663 NULL, NULL, NULL, NULL, FALSE);
3664 if (ok != IMAP_SUCCESS) {
3668 numlist.next = NULL;
3669 numlist.data = GINT_TO_POINTER(uid);
3671 ok = imap_set_message_flags
3672 (IMAP_SESSION(REMOTE_FOLDER(folder)->session),
3673 &numlist, IMAP_FLAG_DELETED, TRUE);
3674 if (ok != IMAP_SUCCESS) {
3675 log_warning(_("can't set deleted flags: %d\n"), uid);
3680 if (!session->uidplus) {
3681 ok = imap_cmd_expunge(session);
3685 uidstr = g_strdup_printf("%u", uid);
3686 ok = imap_cmd_expunge(session);
3689 if (ok != IMAP_SUCCESS) {
3690 log_warning(_("can't expunge\n"));
3695 IMAP_FOLDER_ITEM(item)->uid_list = g_slist_remove(
3696 IMAP_FOLDER_ITEM(item)->uid_list, numlist.data);
3697 dir = folder_item_get_path(item);
3698 if (is_dir_exist(dir))
3699 remove_numbered_files(dir, uid, uid);
3702 return IMAP_SUCCESS;
3705 static gint compare_msginfo(gconstpointer a, gconstpointer b)
3707 return ((MsgInfo *)a)->msgnum - ((MsgInfo *)b)->msgnum;
3710 static guint gslist_find_next_num(MsgNumberList **list, guint num)
3714 g_return_val_if_fail(list != NULL, -1);
3716 for (elem = *list; elem != NULL; elem = g_slist_next(elem))
3717 if (GPOINTER_TO_INT(elem->data) >= num)
3720 return elem != NULL ? GPOINTER_TO_INT(elem->data) : (gint)-1;
3724 * NEW and DELETED flags are not syncronized
3725 * - The NEW/RECENT flags in IMAP folders can not really be directly
3726 * modified by Sylpheed
3727 * - The DELETE/DELETED flag in IMAP and Sylpheed don't have the same
3728 * meaning, in IMAP it always removes the messages from the FolderItem
3729 * in Sylpheed it can mean to move the message to trash
3732 typedef struct _get_flags_data {
3735 MsgInfoList *msginfo_list;
3736 GRelation *msgflags;
3737 gboolean full_search;
3741 static /*gint*/ void *imap_get_flags_thread(void *data)
3743 get_flags_data *stuff = (get_flags_data *)data;
3744 Folder *folder = stuff->folder;
3745 FolderItem *item = stuff->item;
3746 MsgInfoList *msginfo_list = stuff->msginfo_list;
3747 GRelation *msgflags = stuff->msgflags;
3748 gboolean full_search = stuff->full_search;
3749 IMAPSession *session;
3750 GSList *sorted_list = NULL;
3751 GSList *unseen = NULL, *answered = NULL, *flagged = NULL, *deleted = NULL;
3752 GSList *p_unseen, *p_answered, *p_flagged, *p_deleted;
3754 GSList *seq_list, *cur;
3755 gboolean reverse_seen = FALSE;
3758 gint exists_cnt, unseen_cnt;
3759 gboolean selected_folder;
3761 if (folder == NULL || item == NULL) {
3763 return GINT_TO_POINTER(-1);
3766 session = imap_session_get(folder);
3767 if (session == NULL) {
3769 return GINT_TO_POINTER(-1);
3772 selected_folder = (session->mbox != NULL) &&
3773 (!strcmp(session->mbox, item->path));
3775 if (!selected_folder) {
3776 ok = imap_select(session, IMAP_FOLDER(folder), item->path,
3777 &exists_cnt, NULL, &unseen_cnt, NULL, TRUE);
3778 if (ok != IMAP_SUCCESS) {
3781 return GINT_TO_POINTER(-1);
3784 if (unseen_cnt > exists_cnt / 2)
3785 reverse_seen = TRUE;
3788 if (item->unread_msgs > item->total_msgs / 2)
3789 reverse_seen = TRUE;
3792 cmd_buf = g_string_new(NULL);
3794 sorted_list = g_slist_sort(g_slist_copy(msginfo_list), compare_msginfo);
3796 seq_list = imap_get_lep_set_from_msglist(msginfo_list);
3798 struct mailimap_set * set;
3799 set = mailimap_set_new_interval(1, 0);
3800 seq_list = g_slist_append(NULL, set);
3803 for (cur = seq_list; cur != NULL; cur = g_slist_next(cur)) {
3804 struct mailimap_set * imapset;
3805 clist * lep_uidlist;
3808 imapset = cur->data;
3810 r = imap_threaded_search(folder, IMAP_SEARCH_TYPE_SEEN,
3811 full_search ? NULL:imapset, &lep_uidlist);
3814 r = imap_threaded_search(folder,
3815 IMAP_SEARCH_TYPE_UNSEEN,
3816 full_search ? NULL:imapset, &lep_uidlist);
3818 if (r == MAILIMAP_NO_ERROR) {
3821 uidlist = imap_uid_list_from_lep(lep_uidlist);
3822 mailimap_search_result_free(lep_uidlist);
3824 unseen = g_slist_concat(unseen, uidlist);
3827 r = imap_threaded_search(folder, IMAP_SEARCH_TYPE_FLAGGED,
3828 full_search ? NULL:imapset, &lep_uidlist);
3829 if (r == MAILIMAP_NO_ERROR) {
3832 uidlist = imap_uid_list_from_lep(lep_uidlist);
3833 mailimap_search_result_free(lep_uidlist);
3835 flagged = g_slist_concat(flagged, uidlist);
3838 if (item->opened || item->processing_pending || item == folder->inbox) {
3839 r = imap_threaded_search(folder, IMAP_SEARCH_TYPE_ANSWERED,
3840 full_search ? NULL:imapset, &lep_uidlist);
3841 if (r == MAILIMAP_NO_ERROR) {
3844 uidlist = imap_uid_list_from_lep(lep_uidlist);
3845 mailimap_search_result_free(lep_uidlist);
3847 answered = g_slist_concat(answered, uidlist);
3850 r = imap_threaded_search(folder, IMAP_SEARCH_TYPE_DELETED,
3851 full_search ? NULL:imapset, &lep_uidlist);
3852 if (r == MAILIMAP_NO_ERROR) {
3855 uidlist = imap_uid_list_from_lep(lep_uidlist);
3856 mailimap_search_result_free(lep_uidlist);
3858 deleted = g_slist_concat(deleted, uidlist);
3864 p_answered = answered;
3865 p_flagged = flagged;
3866 p_deleted = deleted;
3868 for (elem = sorted_list; elem != NULL; elem = g_slist_next(elem)) {
3873 msginfo = (MsgInfo *) elem->data;
3874 flags = msginfo->flags.perm_flags;
3875 wasnew = (flags & MSG_NEW);
3876 if (item->opened || item->processing_pending || item == folder->inbox) {
3877 flags &= ~((reverse_seen ? 0 : MSG_UNREAD | MSG_NEW) | MSG_REPLIED | MSG_MARKED);
3879 flags &= ~((reverse_seen ? 0 : MSG_UNREAD | MSG_NEW | MSG_MARKED));
3882 flags |= MSG_UNREAD | (wasnew ? MSG_NEW : 0);
3883 if (gslist_find_next_num(&p_unseen, msginfo->msgnum) == msginfo->msgnum) {
3884 if (!reverse_seen) {
3885 flags |= MSG_UNREAD | (wasnew ? MSG_NEW : 0);
3887 flags &= ~(MSG_UNREAD | MSG_NEW);
3891 if (gslist_find_next_num(&p_flagged, msginfo->msgnum) == msginfo->msgnum)
3892 flags |= MSG_MARKED;
3894 flags &= ~MSG_MARKED;
3896 if (item->opened || item->processing_pending || item == folder->inbox) {
3897 if (gslist_find_next_num(&p_answered, msginfo->msgnum) == msginfo->msgnum)
3898 flags |= MSG_REPLIED;
3900 flags &= ~MSG_REPLIED;
3901 if (gslist_find_next_num(&p_deleted, msginfo->msgnum) == msginfo->msgnum)
3902 flags |= MSG_DELETED;
3904 flags &= ~MSG_DELETED;
3906 g_relation_insert(msgflags, msginfo, GINT_TO_POINTER(flags));
3909 imap_lep_set_free(seq_list);
3910 g_slist_free(flagged);
3911 g_slist_free(deleted);
3912 g_slist_free(answered);
3913 g_slist_free(unseen);
3914 g_slist_free(sorted_list);
3915 g_string_free(cmd_buf, TRUE);
3919 return GINT_TO_POINTER(0);
3922 static gint imap_get_flags(Folder *folder, FolderItem *item,
3923 MsgInfoList *msginfo_list, GRelation *msgflags)
3926 get_flags_data *data = g_new0(get_flags_data, 1);
3928 data->folder = folder;
3930 data->msginfo_list = msginfo_list;
3931 data->msgflags = msgflags;
3932 data->full_search = FALSE;
3934 GSList *tmp = NULL, *cur;
3936 if (prefs_common.work_offline &&
3937 !inc_offline_should_override(
3938 _("Claws Mail needs network access in order "
3939 "to access the IMAP server."))) {
3944 tmp = folder_item_get_msg_list(item);
3946 if (g_slist_length(tmp) <= g_slist_length(msginfo_list))
3947 data->full_search = TRUE;
3949 for (cur = tmp; cur; cur = cur->next)
3950 procmsg_msginfo_free((MsgInfo *)cur->data);
3954 result = GPOINTER_TO_INT(imap_get_flags_thread(data));
3961 static gboolean process_flags(gpointer key, gpointer value, gpointer user_data)
3963 gboolean flags_set = GPOINTER_TO_INT(user_data);
3964 gint flags_value = GPOINTER_TO_INT(key);
3965 hashtable_data *data = (hashtable_data *)value;
3966 IMAPFolderItem *_item = data->item;
3967 FolderItem *item = (FolderItem *)_item;
3968 gint ok = IMAP_ERROR;
3969 IMAPSession *session = imap_session_get(item->folder);
3971 data->msglist = g_slist_reverse(data->msglist);
3973 debug_print("IMAP %ssetting flags to %d for %d messages\n",
3976 g_slist_length(data->msglist));
3980 ok = imap_select(session, IMAP_FOLDER(item->folder), item->path,
3981 NULL, NULL, NULL, NULL, FALSE);
3983 if (ok == IMAP_SUCCESS) {
3984 imap_set_message_flags(data->session, data->msglist, flags_value, flags_set);
3986 g_warning("can't select mailbox %s\n", item->path);
3990 g_slist_free(data->msglist);
3995 static void process_hashtable(IMAPFolderItem *item)
3997 if (item->flags_set_table) {
3998 g_hash_table_foreach_remove(item->flags_set_table, process_flags, GINT_TO_POINTER(TRUE));
3999 g_hash_table_destroy(item->flags_set_table);
4000 item->flags_set_table = NULL;
4002 if (item->flags_unset_table) {
4003 g_hash_table_foreach_remove(item->flags_unset_table, process_flags, GINT_TO_POINTER(FALSE));
4004 g_hash_table_destroy(item->flags_unset_table);
4005 item->flags_unset_table = NULL;
4009 static void imap_set_batch (Folder *folder, FolderItem *_item, gboolean batch)
4011 IMAPFolderItem *item = (IMAPFolderItem *)_item;
4013 g_return_if_fail(item != NULL);
4015 if (item->batching == batch)
4019 item->batching = TRUE;
4020 debug_print("IMAP switching to batch mode\n");
4021 if (!item->flags_set_table) {
4022 item->flags_set_table = g_hash_table_new(NULL, g_direct_equal);
4024 if (!item->flags_unset_table) {
4025 item->flags_unset_table = g_hash_table_new(NULL, g_direct_equal);
4028 debug_print("IMAP switching away from batch mode\n");
4030 process_hashtable(item);
4031 item->batching = FALSE;
4037 /* data types conversion libetpan <-> claws */
4041 #define ETPAN_IMAP_MB_MARKED 1
4042 #define ETPAN_IMAP_MB_UNMARKED 2
4043 #define ETPAN_IMAP_MB_NOSELECT 4
4044 #define ETPAN_IMAP_MB_NOINFERIORS 8
4046 static int imap_flags_to_flags(struct mailimap_mbx_list_flags * imap_flags)
4052 if (imap_flags->mbf_type == MAILIMAP_MBX_LIST_FLAGS_SFLAG) {
4053 switch (imap_flags->mbf_sflag) {
4054 case MAILIMAP_MBX_LIST_SFLAG_MARKED:
4055 flags |= ETPAN_IMAP_MB_MARKED;
4057 case MAILIMAP_MBX_LIST_SFLAG_NOSELECT:
4058 flags |= ETPAN_IMAP_MB_NOSELECT;
4060 case MAILIMAP_MBX_LIST_SFLAG_UNMARKED:
4061 flags |= ETPAN_IMAP_MB_UNMARKED;
4066 for(cur = clist_begin(imap_flags->mbf_oflags) ; cur != NULL ;
4067 cur = clist_next(cur)) {
4068 struct mailimap_mbx_list_oflag * oflag;
4070 oflag = clist_content(cur);
4072 switch (oflag->of_type) {
4073 case MAILIMAP_MBX_LIST_OFLAG_NOINFERIORS:
4074 flags |= ETPAN_IMAP_MB_NOINFERIORS;
4082 static GSList * imap_list_from_lep(IMAPFolder * folder,
4083 clist * list, const gchar * real_path, gboolean all)
4086 GSList * item_list = NULL, *llast = NULL;
4088 for(iter = clist_begin(list) ; iter != NULL ;
4089 iter = clist_next(iter)) {
4090 struct mailimap_mailbox_list * mb;
4098 FolderItem *new_item;
4100 mb = clist_content(iter);
4106 if (mb->mb_flag != NULL)
4107 flags = imap_flags_to_flags(mb->mb_flag);
4109 delimiter = mb->mb_delimiter;
4112 dup_name = strdup(name);
4113 if (delimiter != '\0')
4114 subst_char(dup_name, delimiter, '/');
4116 base = g_path_get_basename(dup_name);
4117 if (base[0] == '.') {
4123 if (!all && strcmp(dup_name, real_path) == 0) {
4129 if (!all && dup_name[strlen(dup_name)-1] == '/') {
4135 loc_name = imap_modified_utf7_to_utf8(base);
4136 loc_path = imap_modified_utf7_to_utf8(dup_name);
4138 new_item = folder_item_new(FOLDER(folder), loc_name, loc_path);
4139 if ((flags & ETPAN_IMAP_MB_NOINFERIORS) != 0)
4140 new_item->no_sub = TRUE;
4141 if (strcmp(dup_name, "INBOX") != 0 &&
4142 ((flags & ETPAN_IMAP_MB_NOSELECT) != 0))
4143 new_item->no_select = TRUE;
4145 if (item_list == NULL)
4146 llast = item_list = g_slist_append(item_list, new_item);
4148 llast = g_slist_append(llast, new_item);
4149 llast = llast->next;
4151 debug_print("folder '%s' found.\n", loc_path);
4162 static GSList * imap_get_lep_set_from_numlist(MsgNumberList *numlist)
4164 GSList *sorted_list, *cur;
4165 guint first, last, next;
4166 GSList *ret_list = NULL, *llast = NULL;
4168 struct mailimap_set * current_set;
4169 unsigned int item_count;
4171 if (numlist == NULL)
4175 current_set = mailimap_set_new_empty();
4177 sorted_list = g_slist_copy(numlist);
4178 sorted_list = g_slist_sort(sorted_list, g_int_compare);
4180 first = GPOINTER_TO_INT(sorted_list->data);
4183 for (cur = sorted_list; cur != NULL; cur = g_slist_next(cur)) {
4184 if (GPOINTER_TO_INT(cur->data) == 0)
4189 last = GPOINTER_TO_INT(cur->data);
4191 next = GPOINTER_TO_INT(cur->next->data);
4195 if (last + 1 != next || next == 0) {
4197 struct mailimap_set_item * item;
4198 item = mailimap_set_item_new(first, last);
4199 mailimap_set_add(current_set, item);
4204 if (count >= IMAP_SET_MAX_COUNT) {
4205 if (ret_list == NULL)
4206 llast = ret_list = g_slist_append(ret_list,
4209 llast = g_slist_append(llast, current_set);
4210 llast = llast->next;
4212 current_set = mailimap_set_new_empty();
4219 if (clist_count(current_set->set_list) > 0) {
4220 ret_list = g_slist_append(ret_list,
4224 g_slist_free(sorted_list);
4229 static GSList * imap_get_lep_set_from_msglist(MsgInfoList *msglist)
4231 MsgNumberList *numlist = NULL;
4235 for (cur = msglist; cur != NULL; cur = g_slist_next(cur)) {
4236 MsgInfo *msginfo = (MsgInfo *) cur->data;
4238 numlist = g_slist_prepend(numlist, GINT_TO_POINTER(msginfo->msgnum));
4240 numlist = g_slist_reverse(numlist);
4241 seq_list = imap_get_lep_set_from_numlist(numlist);
4242 g_slist_free(numlist);
4247 static GSList * imap_uid_list_from_lep(clist * list)
4254 for(iter = clist_begin(list) ; iter != NULL ;
4255 iter = clist_next(iter)) {
4258 puid = clist_content(iter);
4259 result = g_slist_prepend(result, GINT_TO_POINTER(* puid));
4262 result = g_slist_reverse(result);
4266 static GSList * imap_uid_list_from_lep_tab(carray * list)
4273 for(i = 0 ; i < carray_count(list) ; i ++) {
4276 puid = carray_get(list, i);
4277 result = g_slist_prepend(result, GINT_TO_POINTER(* puid));
4279 result = g_slist_reverse(result);
4283 static MsgInfo *imap_envelope_from_lep(struct imap_fetch_env_info * info,
4286 MsgInfo *msginfo = NULL;
4289 MsgFlags flags = {0, 0};
4291 if (info->headers == NULL)
4294 MSG_SET_TMP_FLAGS(flags, MSG_IMAP);
4295 if (folder_has_parent_of_type(item, F_QUEUE)) {
4296 MSG_SET_TMP_FLAGS(flags, MSG_QUEUED);
4297 } else if (folder_has_parent_of_type(item, F_DRAFT)) {
4298 MSG_SET_TMP_FLAGS(flags, MSG_DRAFT);
4300 flags.perm_flags = info->flags;
4304 msginfo = procheader_parse_str(info->headers, flags, FALSE, FALSE);
4307 msginfo->msgnum = uid;
4308 msginfo->size = size;
4314 static void imap_lep_set_free(GSList *seq_list)
4318 for(cur = seq_list ; cur != NULL ; cur = g_slist_next(cur)) {
4319 struct mailimap_set * imapset;
4321 imapset = cur->data;
4322 mailimap_set_free(imapset);
4324 g_slist_free(seq_list);
4327 static struct mailimap_flag_list * imap_flag_to_lep(IMAPFlags flags)
4329 struct mailimap_flag_list * flag_list;
4331 flag_list = mailimap_flag_list_new_empty();
4333 if (IMAP_IS_SEEN(flags))
4334 mailimap_flag_list_add(flag_list,
4335 mailimap_flag_new_seen());
4336 if (IMAP_IS_ANSWERED(flags))
4337 mailimap_flag_list_add(flag_list,
4338 mailimap_flag_new_answered());
4339 if (IMAP_IS_FLAGGED(flags))
4340 mailimap_flag_list_add(flag_list,
4341 mailimap_flag_new_flagged());
4342 if (IMAP_IS_DELETED(flags))
4343 mailimap_flag_list_add(flag_list,
4344 mailimap_flag_new_deleted());
4345 if (IMAP_IS_DRAFT(flags))
4346 mailimap_flag_list_add(flag_list,
4347 mailimap_flag_new_draft());
4352 guint imap_folder_get_refcnt(Folder *folder)
4354 return ((IMAPFolder *)folder)->refcnt;
4357 void imap_folder_ref(Folder *folder)
4359 ((IMAPFolder *)folder)->refcnt++;
4362 void imap_disconnect_all(void)
4365 for (list = account_get_list(); list != NULL; list = list->next) {
4366 PrefsAccount *account = list->data;
4367 if (account->protocol == A_IMAP4) {
4368 RemoteFolder *folder = (RemoteFolder *)account->folder;
4369 if (folder && folder->session) {
4370 IMAPSession *session = (IMAPSession *)folder->session;
4371 imap_threaded_disconnect(FOLDER(folder));
4372 SESSION(session)->state = SESSION_DISCONNECTED;
4373 session_destroy(SESSION(session));
4374 folder->session = NULL;
4380 void imap_folder_unref(Folder *folder)
4382 if (((IMAPFolder *)folder)->refcnt > 0)
4383 ((IMAPFolder *)folder)->refcnt--;
4386 #else /* HAVE_LIBETPAN */
4388 static FolderClass imap_class;
4390 static XMLTag *imap_item_get_xml(Folder *folder, FolderItem *item);
4391 static void imap_item_set_xml(Folder *folder, FolderItem *item, XMLTag *tag);
4393 static Folder *imap_folder_new (const gchar *name,
4396 static gboolean missing_imap_warning = TRUE;
4397 if (missing_imap_warning) {
4398 missing_imap_warning = FALSE;
4400 _("You have one or more IMAP accounts "
4401 "defined. However this version of "
4402 "Claws Mail has been built without "
4403 "IMAP support; your IMAP account(s) are "
4405 "You probably need to "
4406 "install libetpan and recompile "
4411 static gint imap_create_tree (Folder *folder)
4415 static FolderItem *imap_create_folder (Folder *folder,
4421 static gint imap_rename_folder (Folder *folder,
4428 gchar imap_get_path_separator_for_item(FolderItem *item)
4433 FolderClass *imap_get_class(void)
4435 if (imap_class.idstr == NULL) {
4436 imap_class.type = F_IMAP;
4437 imap_class.idstr = "imap";
4438 imap_class.uistr = "IMAP4";
4440 imap_class.new_folder = imap_folder_new;
4441 imap_class.create_tree = imap_create_tree;
4442 imap_class.create_folder = imap_create_folder;
4443 imap_class.rename_folder = imap_rename_folder;
4445 imap_class.set_xml = folder_set_xml;
4446 imap_class.get_xml = folder_get_xml;
4447 imap_class.item_set_xml = imap_item_set_xml;
4448 imap_class.item_get_xml = imap_item_get_xml;
4449 /* nothing implemented */
4455 void imap_disconnect_all(void)
4461 void imap_synchronise(FolderItem *item)
4463 imap_gtk_synchronise(item);
4466 static void imap_item_set_xml(Folder *folder, FolderItem *item, XMLTag *tag)
4468 #ifdef HAVE_LIBETPAN
4471 folder_item_set_xml(folder, item, tag);
4473 #ifdef HAVE_LIBETPAN
4474 for (cur = tag->attr; cur != NULL; cur = g_list_next(cur)) {
4475 XMLAttr *attr = (XMLAttr *) cur->data;
4477 if (!attr || !attr->name || !attr->value) continue;
4478 if (!strcmp(attr->name, "uidnext"))
4479 IMAP_FOLDER_ITEM(item)->uid_next = atoi(attr->value);
4484 static XMLTag *imap_item_get_xml(Folder *folder, FolderItem *item)
4488 tag = folder_item_get_xml(folder, item);
4490 #ifdef HAVE_LIBETPAN
4491 xml_tag_add_attr(tag, xml_attr_new_int("uidnext",
4492 IMAP_FOLDER_ITEM(item)->uid_next));