2 * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2006 Hiroyuki Yamamoto and the Sylpheed-Claws team
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
27 #include <glib/gi18n.h>
56 #include "procheader.h"
57 #include "prefs_account.h"
62 #include "prefs_common.h"
63 #include "inputdialog.h"
65 #include "remotefolder.h"
66 #include "alertpanel.h"
68 #include "statusbar.h"
70 #include "imap-thread.h"
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 <-> sylpheed */
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");
608 debug_print("capabilities:\n"
612 imap_has_capability(session, "ANONYMOUS"),
613 imap_has_capability(session, "CRAM-MD5"),
614 imap_has_capability(session, "LOGIN"));
615 if (imap_has_capability(session, "CRAM-MD5"))
616 ok = imap_cmd_login(session, user, pass, "CRAM-MD5");
617 if (ok == IMAP_ERROR) /* we always try LOGIN before giving up */
618 ok = imap_cmd_login(session, user, pass, "LOGIN");
621 if (ok == IMAP_SUCCESS)
622 session->authenticated = TRUE;
624 if (type == IMAP_AUTH_CRAM_MD5) {
625 ext_info = _("\n\nCRAM-MD5 logins only work if libetpan has been "
626 "compiled with SASL support and the "
627 "CRAM-MD5 SASL plugin is installed.");
630 if (time(NULL) - last_login_err > 10) {
631 if (!prefs_common.no_recv_err_panel) {
632 alertpanel_error(_("Connection to %s failed: "
634 SESSION(session)->server, ext_info);
636 log_error(_("Connection to %s failed: "
637 "login refused.%s\n"),
638 SESSION(session)->server, ext_info);
641 last_login_err = time(NULL);
646 static IMAPSession *imap_reconnect_if_possible(Folder *folder, IMAPSession *session)
648 RemoteFolder *rfolder = REMOTE_FOLDER(folder);
649 /* Check if this is the first try to establish a
650 connection, if yes we don't try to reconnect */
651 debug_print("reconnecting\n");
652 if (rfolder->session == NULL) {
653 log_warning(_("Connecting to %s failed"),
654 folder->account->recv_server);
655 session_destroy(SESSION(session));
658 log_warning(_("IMAP4 connection to %s has been"
659 " disconnected. Reconnecting...\n"),
660 folder->account->recv_server);
661 statusbar_print_all(_("IMAP4 connection to %s has been"
662 " disconnected. Reconnecting...\n"),
663 folder->account->recv_server);
664 SESSION(session)->state = SESSION_DISCONNECTED;
665 session_destroy(SESSION(session));
666 /* Clear folders session to make imap_session_get create
667 a new session, because of rfolder->session == NULL
668 it will not try to reconnect again and so avoid an
670 rfolder->session = NULL;
671 session = imap_session_get(folder);
672 rfolder->session = SESSION(session);
678 #define lock_session() {\
679 debug_print("locking session\n"); \
680 session->busy = TRUE;\
683 #define unlock_session() {\
684 debug_print("unlocking session\n"); \
685 session->busy = FALSE;\
688 static IMAPSession *imap_session_get(Folder *folder)
690 RemoteFolder *rfolder = REMOTE_FOLDER(folder);
691 IMAPSession *session = NULL;
693 g_return_val_if_fail(folder != NULL, NULL);
694 g_return_val_if_fail(FOLDER_CLASS(folder) == &imap_class, NULL);
695 g_return_val_if_fail(folder->account != NULL, NULL);
697 if (prefs_common.work_offline &&
698 !inc_offline_should_override(
699 _("Sylpheed-Claws needs network access in order "
700 "to access the IMAP server."))) {
704 /* Make sure we have a session */
705 if (rfolder->session != NULL) {
706 session = IMAP_SESSION(rfolder->session);
707 /* don't do that yet...
712 imap_reset_uid_lists(folder);
713 if (time(NULL) - rfolder->last_failure <= 2)
715 session = imap_session_new(folder, folder->account);
717 if(session == NULL) {
718 rfolder->last_failure = time(NULL);
722 /* Make sure session is authenticated */
723 if (!IMAP_SESSION(session)->authenticated)
724 imap_session_authenticate(IMAP_SESSION(session), folder->account);
726 if (!IMAP_SESSION(session)->authenticated) {
727 imap_threaded_disconnect(session->folder);
728 SESSION(session)->state = SESSION_DISCONNECTED;
729 session_destroy(SESSION(session));
730 rfolder->session = NULL;
731 rfolder->last_failure = time(NULL);
735 /* I think the point of this code is to avoid sending a
736 * keepalive if we've used the session recently and therefore
737 * think it's still alive. Unfortunately, most of the code
738 * does not yet check for errors on the socket, and so if the
739 * connection drops we don't notice until the timeout expires.
740 * A better solution than sending a NOOP every time would be
741 * for every command to be prepared to retry until it is
742 * successfully sent. -- mbp */
743 if (time(NULL) - SESSION(session)->last_access_time > SESSION_TIMEOUT_INTERVAL) {
744 /* verify that the session is still alive */
745 if (imap_cmd_noop(session) != IMAP_SUCCESS) {
746 debug_print("disconnected!\n");
747 session = imap_reconnect_if_possible(folder, session);
751 rfolder->session = SESSION(session);
753 return IMAP_SESSION(session);
756 static IMAPSession *imap_session_new(Folder * folder,
757 const PrefsAccount *account)
759 IMAPSession *session;
762 int authenticated = FALSE;
765 /* FIXME: IMAP over SSL only... */
768 port = account->set_imapport ? account->imapport
769 : account->ssl_imap == SSL_TUNNEL ? IMAPS_PORT : IMAP4_PORT;
770 ssl_type = account->ssl_imap;
772 if (account->ssl_imap != SSL_NONE) {
773 if (alertpanel_full(_("Insecure connection"),
774 _("This connection is configured to be secured "
775 "using SSL, but SSL is not available in this "
776 "build of Sylpheed-Claws. \n\n"
777 "Do you want to continue connecting to this "
778 "server? The communication would not be "
780 GTK_STOCK_CANCEL, _("Con_tinue connecting"),
781 NULL, FALSE, NULL, ALERT_WARNING,
782 G_ALERTDEFAULT) != G_ALERTALTERNATE)
785 port = account->set_imapport ? account->imapport
790 statusbar_print_all(_("Connecting to IMAP4 server: %s..."), folder->account->recv_server);
791 if (account->set_tunnelcmd) {
792 r = imap_threaded_connect_cmd(folder,
794 account->recv_server,
799 if (ssl_type == SSL_TUNNEL) {
800 r = imap_threaded_connect_ssl(folder,
801 account->recv_server,
807 r = imap_threaded_connect(folder,
808 account->recv_server,
814 if (r == MAILIMAP_NO_ERROR_AUTHENTICATED) {
815 authenticated = TRUE;
817 else if (r == MAILIMAP_NO_ERROR_NON_AUTHENTICATED) {
818 authenticated = FALSE;
821 if(!prefs_common.no_recv_err_panel) {
822 alertpanel_error(_("Can't connect to IMAP4 server: %s:%d"),
823 account->recv_server, port);
825 log_error(_("Can't connect to IMAP4 server: %s:%d\n"),
826 account->recv_server, port);
832 session = g_new0(IMAPSession, 1);
833 session_init(SESSION(session));
834 SESSION(session)->type = SESSION_IMAP;
835 SESSION(session)->server = g_strdup(account->recv_server);
836 SESSION(session)->sock = NULL;
838 SESSION(session)->destroy = imap_session_destroy;
840 session->capability = NULL;
842 session->authenticated = authenticated;
843 session->mbox = NULL;
844 session->cmd_count = 0;
845 session->folder = folder;
846 IMAP_FOLDER(session->folder)->last_seen_separator = 0;
849 if (account->ssl_imap == SSL_STARTTLS) {
852 ok = imap_cmd_starttls(session);
853 if (ok != IMAP_SUCCESS) {
854 log_warning(_("Can't start TLS session.\n"));
855 session_destroy(SESSION(session));
859 imap_free_capabilities(session);
860 session->authenticated = FALSE;
861 session->uidplus = FALSE;
862 session->cmd_count = 1;
865 log_message("IMAP connection is %s-authenticated\n",
866 (session->authenticated) ? "pre" : "un");
871 static void imap_session_authenticate(IMAPSession *session,
872 const PrefsAccount *account)
874 gchar *pass, *acc_pass;
875 gboolean failed = FALSE;
877 g_return_if_fail(account->userid != NULL);
878 acc_pass = account->passwd;
881 if (!pass && account->imap_auth_type != IMAP_AUTH_ANON) {
883 tmp_pass = input_dialog_query_password(account->recv_server, account->userid);
886 Xstrdup_a(pass, tmp_pass, {g_free(tmp_pass); return;});
888 } else if (account->imap_auth_type == IMAP_AUTH_ANON) {
891 statusbar_print_all(_("Connecting to IMAP4 server %s...\n"),
892 account->recv_server);
893 if (imap_auth(session, account->userid, pass, account->imap_auth_type) != IMAP_SUCCESS) {
901 if (prefs_common.no_recv_err_panel) {
902 log_error(_("Couldn't login to IMAP server %s."), account->recv_server);
903 mainwindow_show_error();
905 alertpanel_error_log(_("Couldn't login to IMAP server %s."), account->recv_server);
912 session->authenticated = TRUE;
916 static void imap_session_destroy(Session *session)
918 if (session->state != SESSION_DISCONNECTED)
919 imap_threaded_disconnect(IMAP_SESSION(session)->folder);
921 imap_free_capabilities(IMAP_SESSION(session));
922 g_free(IMAP_SESSION(session)->mbox);
923 sock_close(session->sock);
924 session->sock = NULL;
927 static gchar *imap_fetch_msg(Folder *folder, FolderItem *item, gint uid)
929 return imap_fetch_msg_full(folder, item, uid, TRUE, TRUE);
932 static guint get_size_with_crs(MsgInfo *info)
941 fp = procmsg_open_message(info);
945 while (fgets(buf, sizeof (buf), fp) != NULL) {
947 if (!strstr(buf, "\r") && strstr(buf, "\n"))
955 static gchar *imap_fetch_msg_full(Folder *folder, FolderItem *item, gint uid,
956 gboolean headers, gboolean body)
958 gchar *path, *filename;
959 IMAPSession *session;
962 g_return_val_if_fail(folder != NULL, NULL);
963 g_return_val_if_fail(item != NULL, NULL);
968 path = folder_item_get_path(item);
969 if (!is_dir_exist(path))
971 filename = g_strconcat(path, G_DIR_SEPARATOR_S, itos(uid), NULL);
973 debug_print("trying to fetch cached %s\n", filename);
974 if (is_file_exist(filename)) {
975 /* see whether the local file represents the whole message
976 * or not. As the IMAP server reports size with \r chars,
977 * we have to update the local file (UNIX \n only) size */
978 MsgInfo *msginfo = imap_parse_msg(filename, item);
979 MsgInfo *cached = msgcache_get_msg(item->cache,uid);
980 guint have_size = get_size_with_crs(msginfo);
983 debug_print("message %d has been already %scached (%d/%d).\n", uid,
984 have_size >= cached->size ? "fully ":"",
985 have_size, (int)cached->size);
987 if (cached && (cached->size <= have_size || !body)) {
988 procmsg_msginfo_free(cached);
989 procmsg_msginfo_free(msginfo);
990 file_strip_crs(filename);
992 } else if (!cached && time(NULL) - get_file_mtime(filename) < 60) {
993 debug_print("message not cached and file recent, considering file complete\n");
994 procmsg_msginfo_free(msginfo);
995 file_strip_crs(filename);
998 procmsg_msginfo_free(cached);
999 procmsg_msginfo_free(msginfo);
1003 session = imap_session_get(folder);
1012 debug_print("IMAP fetching messages\n");
1013 ok = imap_select(session, IMAP_FOLDER(folder), item->path,
1014 NULL, NULL, NULL, NULL, FALSE);
1015 if (ok != IMAP_SUCCESS) {
1016 g_warning("can't select mailbox %s\n", item->path);
1022 debug_print("getting message %d...\n", uid);
1023 ok = imap_cmd_fetch(session, (guint32)uid, filename, headers, body);
1025 if (ok != IMAP_SUCCESS) {
1026 g_warning("can't fetch message %d\n", uid);
1033 file_strip_crs(filename);
1037 static gint imap_add_msg(Folder *folder, FolderItem *dest,
1038 const gchar *file, MsgFlags *flags)
1042 MsgFileInfo fileinfo;
1044 g_return_val_if_fail(file != NULL, -1);
1046 fileinfo.msginfo = NULL;
1047 fileinfo.file = (gchar *)file;
1048 fileinfo.flags = flags;
1049 file_list.data = &fileinfo;
1050 file_list.next = NULL;
1052 ret = imap_add_msgs(folder, dest, &file_list, NULL);
1056 static gint imap_add_msgs(Folder *folder, FolderItem *dest, GSList *file_list,
1057 GRelation *relation)
1060 IMAPSession *session;
1061 guint32 last_uid = 0;
1063 MsgFileInfo *fileinfo;
1065 gint curnum = 0, total = 0;
1068 g_return_val_if_fail(folder != NULL, -1);
1069 g_return_val_if_fail(dest != NULL, -1);
1070 g_return_val_if_fail(file_list != NULL, -1);
1072 session = imap_session_get(folder);
1077 destdir = imap_get_real_path(IMAP_FOLDER(folder), dest->path);
1079 statusbar_print_all(_("Adding messages..."));
1080 total = g_slist_length(file_list);
1081 for (cur = file_list; cur != NULL; cur = cur->next) {
1082 IMAPFlags iflags = 0;
1083 guint32 new_uid = 0;
1084 gchar *real_file = NULL;
1085 fileinfo = (MsgFileInfo *)cur->data;
1087 statusbar_progress_all(curnum, total, 1);
1090 if (fileinfo->flags) {
1091 if (MSG_IS_MARKED(*fileinfo->flags))
1092 iflags |= IMAP_FLAG_FLAGGED;
1093 if (MSG_IS_REPLIED(*fileinfo->flags))
1094 iflags |= IMAP_FLAG_ANSWERED;
1095 if (!MSG_IS_UNREAD(*fileinfo->flags))
1096 iflags |= IMAP_FLAG_SEEN;
1099 if (real_file == NULL)
1100 real_file = g_strdup(fileinfo->file);
1102 if (folder_has_parent_of_type(dest, F_QUEUE) ||
1103 folder_has_parent_of_type(dest, F_OUTBOX) ||
1104 folder_has_parent_of_type(dest, F_DRAFT) ||
1105 folder_has_parent_of_type(dest, F_TRASH))
1106 iflags |= IMAP_FLAG_SEEN;
1108 ok = imap_cmd_append(session, destdir, real_file, iflags,
1111 if (ok != IMAP_SUCCESS) {
1112 g_warning("can't append message %s\n", real_file);
1116 statusbar_progress_all(0,0,0);
1117 statusbar_pop_all();
1120 debug_print("appended new message as %d\n", new_uid);
1121 /* put the local file in the imapcache, so that we don't
1122 * have to fetch it back later. */
1124 gchar *cache_path = folder_item_get_path(dest);
1125 if (!is_dir_exist(cache_path))
1126 make_dir_hier(cache_path);
1127 if (is_dir_exist(cache_path)) {
1128 gchar *cache_file = g_strconcat(
1129 cache_path, G_DIR_SEPARATOR_S,
1130 itos(new_uid), NULL);
1131 copy_file(real_file, cache_file, TRUE);
1132 debug_print("copied to cache: %s\n", cache_file);
1139 if (relation != NULL)
1140 g_relation_insert(relation, fileinfo->msginfo != NULL ?
1141 (gpointer) fileinfo->msginfo : (gpointer) fileinfo,
1142 GINT_TO_POINTER(dest->last_num + 1));
1144 new_uid = dest->last_num+1;
1146 if (last_uid < new_uid) {
1152 statusbar_progress_all(0,0,0);
1153 statusbar_pop_all();
1155 imap_cmd_expunge(session);
1163 static gint imap_do_copy_msgs(Folder *folder, FolderItem *dest,
1164 MsgInfoList *msglist, GRelation *relation)
1168 GSList *seq_list, *cur;
1170 IMAPSession *session;
1171 gint ok = IMAP_SUCCESS;
1172 GRelation *uid_mapping;
1174 gboolean single = FALSE;
1176 g_return_val_if_fail(folder != NULL, -1);
1177 g_return_val_if_fail(dest != NULL, -1);
1178 g_return_val_if_fail(msglist != NULL, -1);
1180 session = imap_session_get(folder);
1186 msginfo = (MsgInfo *)msglist->data;
1187 if (msglist->next == NULL)
1189 src = msginfo->folder;
1191 g_warning("the src folder is identical to the dest.\n");
1196 if (src->folder != dest->folder) {
1197 GSList *infolist = NULL, *cur;
1199 for (cur = msglist; cur; cur = cur->next) {
1200 msginfo = (MsgInfo *)cur->data;
1201 MsgFileInfo *fileinfo = g_new0(MsgFileInfo, 1);
1202 fileinfo->file = procmsg_get_message_file(msginfo);
1203 fileinfo->flags = &(msginfo->flags);
1204 infolist = g_slist_prepend(infolist, fileinfo);
1206 infolist = g_slist_reverse(infolist);
1207 res = folder_item_add_msgs(dest, infolist, FALSE);
1208 for (cur = infolist; cur; cur = cur->next) {
1209 MsgFileInfo *info = (MsgFileInfo *)cur->data;
1213 g_slist_free(infolist);
1217 ok = imap_select(session, IMAP_FOLDER(folder), msginfo->folder->path,
1218 NULL, NULL, NULL, NULL, FALSE);
1219 if (ok != IMAP_SUCCESS) {
1224 destdir = imap_get_real_path(IMAP_FOLDER(folder), dest->path);
1225 seq_list = imap_get_lep_set_from_msglist(msglist);
1226 uid_mapping = g_relation_new(2);
1227 g_relation_index(uid_mapping, 0, g_direct_hash, g_direct_equal);
1229 statusbar_print_all(_("Copying messages..."));
1230 for (cur = seq_list; cur != NULL; cur = g_slist_next(cur)) {
1231 struct mailimap_set * seq_set;
1232 struct mailimap_set * source;
1233 struct mailimap_set * dest;
1234 seq_set = cur->data;
1236 debug_print("Copying messages from %s to %s ...\n",
1237 src->path, destdir);
1239 ok = imap_cmd_copy(session, seq_set, destdir, uid_mapping,
1242 if (ok == IMAP_SUCCESS) {
1243 if (single && relation && source && dest) {
1244 clistiter *l = clist_begin(source->set_list);
1245 struct mailimap_set_item *i = (struct mailimap_set_item *)clist_content(l);
1246 int snum = i->set_first;
1248 l = clist_begin(dest->set_list);
1249 i = (struct mailimap_set_item *)clist_content(l);
1250 dnum = i->set_first;
1251 g_relation_insert(uid_mapping, GINT_TO_POINTER(snum),
1252 GINT_TO_POINTER(dnum));
1258 mailimap_set_free(source);
1260 mailimap_set_free(dest);
1262 if (ok != IMAP_SUCCESS) {
1263 g_relation_destroy(uid_mapping);
1264 imap_lep_set_free(seq_list);
1270 for (cur = msglist; cur != NULL; cur = g_slist_next(cur)) {
1271 MsgInfo *msginfo = (MsgInfo *)cur->data;
1274 tuples = g_relation_select(uid_mapping,
1275 GINT_TO_POINTER(msginfo->msgnum),
1277 if (tuples->len > 0) {
1278 gint num = GPOINTER_TO_INT(g_tuples_index(tuples, 0, 1));
1279 g_relation_insert(relation, msginfo,
1280 GPOINTER_TO_INT(num));
1283 debug_print("copied new message as %d\n", num);
1284 /* put the local file in the imapcache, so that we don't
1285 * have to fetch it back later. */
1287 gchar *cache_path = folder_item_get_path(msginfo->folder);
1288 gchar *real_file = g_strconcat(
1289 cache_path, G_DIR_SEPARATOR_S,
1290 itos(msginfo->msgnum), NULL);
1291 gchar *cache_file = NULL;
1293 cache_path = folder_item_get_path(dest);
1294 cache_file = g_strconcat(
1295 cache_path, G_DIR_SEPARATOR_S,
1297 if (!is_dir_exist(cache_path))
1298 make_dir_hier(cache_path);
1299 if (is_file_exist(real_file) && is_dir_exist(cache_path)) {
1300 copy_file(real_file, cache_file, TRUE);
1301 debug_print("copied to cache: %s\n", cache_file);
1308 g_relation_insert(relation, msginfo,
1309 GPOINTER_TO_INT(0));
1310 g_tuples_destroy(tuples);
1312 statusbar_pop_all();
1314 g_relation_destroy(uid_mapping);
1315 imap_lep_set_free(seq_list);
1319 IMAP_FOLDER_ITEM(dest)->lastuid = 0;
1320 IMAP_FOLDER_ITEM(dest)->uid_next = 0;
1321 g_slist_free(IMAP_FOLDER_ITEM(dest)->uid_list);
1322 IMAP_FOLDER_ITEM(dest)->uid_list = NULL;
1325 if (ok == IMAP_SUCCESS)
1331 static gint imap_copy_msg(Folder *folder, FolderItem *dest, MsgInfo *msginfo)
1335 g_return_val_if_fail(msginfo != NULL, -1);
1337 msglist.data = msginfo;
1338 msglist.next = NULL;
1340 return imap_copy_msgs(folder, dest, &msglist, NULL);
1343 static gint imap_copy_msgs(Folder *folder, FolderItem *dest,
1344 MsgInfoList *msglist, GRelation *relation)
1349 g_return_val_if_fail(folder != NULL, -1);
1350 g_return_val_if_fail(dest != NULL, -1);
1351 g_return_val_if_fail(msglist != NULL, -1);
1353 msginfo = (MsgInfo *)msglist->data;
1354 g_return_val_if_fail(msginfo->folder != NULL, -1);
1356 ret = imap_do_copy_msgs(folder, dest, msglist, relation);
1361 static gint imap_do_remove_msgs(Folder *folder, FolderItem *dest,
1362 MsgInfoList *msglist, GRelation *relation)
1364 gchar *destdir, *dir;
1365 GSList *numlist = NULL, *cur;
1367 IMAPSession *session;
1368 gint ok = IMAP_SUCCESS;
1369 GRelation *uid_mapping;
1371 g_return_val_if_fail(folder != NULL, -1);
1372 g_return_val_if_fail(dest != NULL, -1);
1373 g_return_val_if_fail(msglist != NULL, -1);
1375 session = imap_session_get(folder);
1380 msginfo = (MsgInfo *)msglist->data;
1382 ok = imap_select(session, IMAP_FOLDER(folder), msginfo->folder->path,
1383 NULL, NULL, NULL, NULL, FALSE);
1384 if (ok != IMAP_SUCCESS) {
1389 destdir = imap_get_real_path(IMAP_FOLDER(folder), dest->path);
1390 for (cur = msglist; cur; cur = cur->next) {
1391 msginfo = (MsgInfo *)cur->data;
1392 if (!MSG_IS_DELETED(msginfo->flags))
1393 numlist = g_slist_prepend(numlist, GINT_TO_POINTER(msginfo->msgnum));
1395 numlist = g_slist_reverse(numlist);
1397 uid_mapping = g_relation_new(2);
1398 g_relation_index(uid_mapping, 0, g_direct_hash, g_direct_equal);
1400 ok = imap_set_message_flags
1401 (IMAP_SESSION(REMOTE_FOLDER(folder)->session),
1402 numlist, IMAP_FLAG_DELETED, TRUE);
1403 if (ok != IMAP_SUCCESS) {
1404 log_warning(_("can't set deleted flags\n"));
1408 ok = imap_cmd_expunge(session);
1409 if (ok != IMAP_SUCCESS) {
1410 log_warning(_("can't expunge\n"));
1415 dir = folder_item_get_path(msginfo->folder);
1416 if (is_dir_exist(dir)) {
1417 for (cur = msglist; cur; cur = cur->next) {
1418 msginfo = (MsgInfo *)cur->data;
1419 remove_numbered_files(dir, msginfo->msgnum, msginfo->msgnum);
1424 g_relation_destroy(uid_mapping);
1425 g_slist_free(numlist);
1429 if (ok == IMAP_SUCCESS)
1435 static gint imap_remove_msgs(Folder *folder, FolderItem *dest,
1436 MsgInfoList *msglist, GRelation *relation)
1440 g_return_val_if_fail(folder != NULL, -1);
1441 g_return_val_if_fail(dest != NULL, -1);
1442 if (msglist == NULL)
1445 msginfo = (MsgInfo *)msglist->data;
1446 g_return_val_if_fail(msginfo->folder != NULL, -1);
1448 return imap_do_remove_msgs(folder, dest, msglist, relation);
1451 static gint imap_remove_all_msg(Folder *folder, FolderItem *item)
1453 GSList *list = folder_item_get_msg_list(item);
1454 gint res = imap_remove_msgs(folder, item, list, NULL);
1455 procmsg_msg_list_free(list);
1459 static gboolean imap_is_msg_changed(Folder *folder, FolderItem *item,
1462 /* TODO: properly implement this method */
1466 static gint imap_close(Folder *folder, FolderItem *item)
1471 static gint imap_scan_tree(Folder *folder)
1473 FolderItem *item = NULL;
1474 IMAPSession *session;
1475 gchar *root_folder = NULL;
1477 g_return_val_if_fail(folder != NULL, -1);
1478 g_return_val_if_fail(folder->account != NULL, -1);
1480 session = imap_session_get(folder);
1482 if (!folder->node) {
1483 folder_tree_destroy(folder);
1484 item = folder_item_new(folder, folder->name, NULL);
1485 item->folder = folder;
1486 folder->node = item->node = g_node_new(item);
1492 if (folder->account->imap_dir && *folder->account->imap_dir) {
1497 Xstrdup_a(root_folder, folder->account->imap_dir, {unlock_session();return -1;});
1498 extract_quote(root_folder, '"');
1499 subst_char(root_folder,
1500 imap_get_path_separator(IMAP_FOLDER(folder),
1503 strtailchomp(root_folder, '/');
1504 real_path = imap_get_real_path
1505 (IMAP_FOLDER(folder), root_folder);
1506 debug_print("IMAP root directory: %s\n", real_path);
1508 /* check if root directory exist */
1510 r = imap_threaded_list(session->folder, "", real_path,
1512 if ((r != MAILIMAP_NO_ERROR) || (clist_count(lep_list) == 0)) {
1513 if (!folder->node) {
1514 item = folder_item_new(folder, folder->name, NULL);
1515 item->folder = folder;
1516 folder->node = item->node = g_node_new(item);
1521 mailimap_list_result_free(lep_list);
1527 item = FOLDER_ITEM(folder->node->data);
1528 if (!item || ((item->path || root_folder) &&
1529 strcmp2(item->path, root_folder) != 0)) {
1530 folder_tree_destroy(folder);
1531 item = folder_item_new(folder, folder->name, root_folder);
1532 item->folder = folder;
1533 folder->node = item->node = g_node_new(item);
1536 imap_scan_tree_recursive(session, FOLDER_ITEM(folder->node->data));
1537 imap_create_missing_folders(folder);
1543 static gint imap_scan_tree_recursive(IMAPSession *session, FolderItem *item)
1546 IMAPFolder *imapfolder;
1547 FolderItem *new_item;
1548 GSList *item_list, *cur;
1551 gchar *wildcard_path;
1557 g_return_val_if_fail(item != NULL, -1);
1558 g_return_val_if_fail(item->folder != NULL, -1);
1559 g_return_val_if_fail(item->no_sub == FALSE, -1);
1561 folder = item->folder;
1562 imapfolder = IMAP_FOLDER(folder);
1564 separator = imap_get_path_separator(imapfolder, item->path);
1566 if (folder->ui_func)
1567 folder->ui_func(folder, item, folder->ui_func_data);
1570 wildcard[0] = separator;
1573 real_path = imap_get_real_path(imapfolder, item->path);
1577 real_path = g_strdup("");
1580 Xstrcat_a(wildcard_path, real_path, wildcard,
1581 {g_free(real_path); return IMAP_ERROR;});
1583 r = imap_threaded_list(folder, "", wildcard_path, &lep_list);
1584 if (r != MAILIMAP_NO_ERROR) {
1588 item_list = imap_list_from_lep(imapfolder,
1589 lep_list, real_path, FALSE);
1590 mailimap_list_result_free(lep_list);
1595 node = item->node->children;
1596 while (node != NULL) {
1597 FolderItem *old_item = FOLDER_ITEM(node->data);
1598 GNode *next = node->next;
1601 for (cur = item_list; cur != NULL; cur = cur->next) {
1602 FolderItem *cur_item = FOLDER_ITEM(cur->data);
1603 if (!strcmp2(old_item->path, cur_item->path)) {
1604 new_item = cur_item;
1609 debug_print("folder '%s' not found. removing...\n",
1611 folder_item_remove(old_item);
1613 old_item->no_sub = new_item->no_sub;
1614 old_item->no_select = new_item->no_select;
1615 if (old_item->no_sub == TRUE && node->children) {
1616 debug_print("folder '%s' doesn't have "
1617 "subfolders. removing...\n",
1619 folder_item_remove_children(old_item);
1626 for (cur = item_list; cur != NULL; cur = cur->next) {
1627 FolderItem *cur_item = FOLDER_ITEM(cur->data);
1630 for (node = item->node->children; node != NULL;
1631 node = node->next) {
1632 if (!strcmp2(FOLDER_ITEM(node->data)->path,
1634 new_item = FOLDER_ITEM(node->data);
1635 folder_item_destroy(cur_item);
1641 new_item = cur_item;
1642 debug_print("new folder '%s' found.\n", new_item->path);
1643 folder_item_append(item, new_item);
1646 if (!strcmp(new_item->path, "INBOX")) {
1647 new_item->stype = F_INBOX;
1648 folder->inbox = new_item;
1649 } else if (!folder_item_parent(item) || item->stype == F_INBOX) {
1652 base = g_path_get_basename(new_item->path);
1654 if (!folder->outbox && !g_ascii_strcasecmp(base, "Sent")) {
1655 new_item->stype = F_OUTBOX;
1656 folder->outbox = new_item;
1657 } else if (!folder->draft && !g_ascii_strcasecmp(base, "Drafts")) {
1658 new_item->stype = F_DRAFT;
1659 folder->draft = new_item;
1660 } else if (!folder->queue && !g_ascii_strcasecmp(base, "Queue")) {
1661 new_item->stype = F_QUEUE;
1662 folder->queue = new_item;
1663 } else if (!folder->trash && !g_ascii_strcasecmp(base, "Trash")) {
1664 new_item->stype = F_TRASH;
1665 folder->trash = new_item;
1670 if (new_item->no_sub == FALSE)
1671 imap_scan_tree_recursive(session, new_item);
1674 g_slist_free(item_list);
1676 return IMAP_SUCCESS;
1679 static gint imap_create_tree(Folder *folder)
1681 g_return_val_if_fail(folder != NULL, -1);
1682 g_return_val_if_fail(folder->node != NULL, -1);
1683 g_return_val_if_fail(folder->node->data != NULL, -1);
1684 g_return_val_if_fail(folder->account != NULL, -1);
1686 imap_scan_tree(folder);
1687 imap_create_missing_folders(folder);
1692 static void imap_create_missing_folders(Folder *folder)
1694 g_return_if_fail(folder != NULL);
1697 folder->inbox = imap_create_special_folder
1698 (folder, F_INBOX, "INBOX");
1700 folder->trash = imap_create_special_folder
1701 (folder, F_TRASH, "Trash");
1703 folder->queue = imap_create_special_folder
1704 (folder, F_QUEUE, "Queue");
1705 if (!folder->outbox)
1706 folder->outbox = imap_create_special_folder
1707 (folder, F_OUTBOX, "Sent");
1709 folder->draft = imap_create_special_folder
1710 (folder, F_DRAFT, "Drafts");
1713 static FolderItem *imap_create_special_folder(Folder *folder,
1714 SpecialFolderItemType stype,
1718 FolderItem *new_item;
1720 g_return_val_if_fail(folder != NULL, NULL);
1721 g_return_val_if_fail(folder->node != NULL, NULL);
1722 g_return_val_if_fail(folder->node->data != NULL, NULL);
1723 g_return_val_if_fail(folder->account != NULL, NULL);
1724 g_return_val_if_fail(name != NULL, NULL);
1726 item = FOLDER_ITEM(folder->node->data);
1727 new_item = imap_create_folder(folder, item, name);
1730 g_warning("Can't create '%s'\n", name);
1731 if (!folder->inbox) return NULL;
1733 new_item = imap_create_folder(folder, folder->inbox, name);
1735 g_warning("Can't create '%s' under INBOX\n", name);
1737 new_item->stype = stype;
1739 new_item->stype = stype;
1744 static gchar *imap_folder_get_path(Folder *folder)
1748 g_return_val_if_fail(folder != NULL, NULL);
1749 g_return_val_if_fail(folder->account != NULL, NULL);
1751 folder_path = g_strconcat(get_imap_cache_dir(),
1753 folder->account->recv_server,
1755 folder->account->userid,
1761 static gchar *imap_item_get_path(Folder *folder, FolderItem *item)
1763 gchar *folder_path, *path;
1765 g_return_val_if_fail(folder != NULL, NULL);
1766 g_return_val_if_fail(item != NULL, NULL);
1767 folder_path = imap_folder_get_path(folder);
1769 g_return_val_if_fail(folder_path != NULL, NULL);
1770 if (folder_path[0] == G_DIR_SEPARATOR) {
1772 path = g_strconcat(folder_path, G_DIR_SEPARATOR_S,
1775 path = g_strdup(folder_path);
1778 path = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S,
1779 folder_path, G_DIR_SEPARATOR_S,
1782 path = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S,
1785 g_free(folder_path);
1790 static FolderItem *imap_create_folder(Folder *folder, FolderItem *parent,
1793 gchar *dirpath, *imap_path;
1794 IMAPSession *session;
1795 FolderItem *new_item;
1800 gboolean no_select = FALSE, no_sub = FALSE;
1802 g_return_val_if_fail(folder != NULL, NULL);
1803 g_return_val_if_fail(folder->account != NULL, NULL);
1804 g_return_val_if_fail(parent != NULL, NULL);
1805 g_return_val_if_fail(name != NULL, NULL);
1807 session = imap_session_get(folder);
1813 if (!folder_item_parent(parent) && strcmp(name, "INBOX") == 0) {
1814 dirpath = g_strdup(name);
1815 }else if (parent->path)
1816 dirpath = g_strconcat(parent->path, "/", name, NULL);
1817 else if ((p = strchr(name, '/')) != NULL && *(p + 1) != '\0')
1818 dirpath = g_strdup(name);
1819 else if (folder->account->imap_dir && *folder->account->imap_dir) {
1822 Xstrdup_a(imap_dir, folder->account->imap_dir, {unlock_session();return NULL;});
1823 strtailchomp(imap_dir, '/');
1824 dirpath = g_strconcat(imap_dir, "/", name, NULL);
1826 dirpath = g_strdup(name);
1830 /* keep trailing directory separator to create a folder that contains
1832 imap_path = imap_utf8_to_modified_utf7(dirpath);
1834 strtailchomp(dirpath, '/');
1835 Xstrdup_a(new_name, name, {
1840 separator = imap_get_path_separator(IMAP_FOLDER(folder), imap_path);
1841 imap_path_separator_subst(imap_path, separator);
1842 /* remove trailing / for display */
1843 strtailchomp(new_name, '/');
1845 if (strcmp(dirpath, "INBOX") != 0) {
1847 gboolean exist = FALSE;
1851 argbuf = g_ptr_array_new();
1852 r = imap_threaded_list(folder, "", imap_path, &lep_list);
1853 if (r != MAILIMAP_NO_ERROR) {
1854 log_warning(_("can't create mailbox: LIST failed\n"));
1857 ptr_array_free_strings(argbuf);
1858 g_ptr_array_free(argbuf, TRUE);
1863 if (clist_count(lep_list) > 0)
1865 mailimap_list_result_free(lep_list);
1868 ok = imap_cmd_create(session, imap_path);
1869 if (ok != IMAP_SUCCESS) {
1870 log_warning(_("can't create mailbox\n"));
1876 r = imap_threaded_list(folder, "", imap_path, &lep_list);
1877 if (r == MAILIMAP_NO_ERROR) {
1878 GSList *item_list = imap_list_from_lep(IMAP_FOLDER(folder),
1879 lep_list, dirpath, TRUE);
1881 FolderItem *cur_item = FOLDER_ITEM(item_list->data);
1882 no_select = cur_item->no_select;
1883 no_sub = cur_item->no_sub;
1884 g_slist_free(item_list);
1886 mailimap_list_result_free(lep_list);
1893 /* just get flags */
1894 r = imap_threaded_list(folder, "", "INBOX", &lep_list);
1895 if (r == MAILIMAP_NO_ERROR) {
1896 GSList *item_list = imap_list_from_lep(IMAP_FOLDER(folder),
1897 lep_list, dirpath, TRUE);
1899 FolderItem *cur_item = FOLDER_ITEM(item_list->data);
1900 no_select = cur_item->no_select;
1901 no_sub = cur_item->no_sub;
1902 g_slist_free(item_list);
1904 mailimap_list_result_free(lep_list);
1908 new_item = folder_item_new(folder, new_name, dirpath);
1909 new_item->no_select = no_select;
1910 new_item->no_sub = no_sub;
1911 folder_item_append(parent, new_item);
1915 dirpath = folder_item_get_path(new_item);
1916 if (!is_dir_exist(dirpath))
1917 make_dir_hier(dirpath);
1923 static gint imap_rename_folder(Folder *folder, FolderItem *item,
1928 gchar *real_oldpath;
1929 gchar *real_newpath;
1931 gchar *old_cache_dir;
1932 gchar *new_cache_dir;
1933 IMAPSession *session;
1936 gint exists, recent, unseen;
1937 guint32 uid_validity;
1939 g_return_val_if_fail(folder != NULL, -1);
1940 g_return_val_if_fail(item != NULL, -1);
1941 g_return_val_if_fail(item->path != NULL, -1);
1942 g_return_val_if_fail(name != NULL, -1);
1944 session = imap_session_get(folder);
1950 if (strchr(name, imap_get_path_separator(IMAP_FOLDER(folder), item->path)) != NULL) {
1951 g_warning(_("New folder name must not contain the namespace "
1957 real_oldpath = imap_get_real_path(IMAP_FOLDER(folder), item->path);
1959 g_free(session->mbox);
1960 session->mbox = NULL;
1961 ok = imap_cmd_examine(session, "INBOX",
1962 &exists, &recent, &unseen, &uid_validity, FALSE);
1963 if (ok != IMAP_SUCCESS) {
1964 g_free(real_oldpath);
1969 separator = imap_get_path_separator(IMAP_FOLDER(folder), item->path);
1970 if (strchr(item->path, G_DIR_SEPARATOR)) {
1971 dirpath = g_path_get_dirname(item->path);
1972 newpath = g_strconcat(dirpath, G_DIR_SEPARATOR_S, name, NULL);
1975 newpath = g_strdup(name);
1977 real_newpath = imap_utf8_to_modified_utf7(newpath);
1978 imap_path_separator_subst(real_newpath, separator);
1980 ok = imap_cmd_rename(session, real_oldpath, real_newpath);
1981 if (ok != IMAP_SUCCESS) {
1982 log_warning(_("can't rename mailbox: %s to %s\n"),
1983 real_oldpath, real_newpath);
1984 g_free(real_oldpath);
1986 g_free(real_newpath);
1992 item->name = g_strdup(name);
1994 old_cache_dir = folder_item_get_path(item);
1996 paths[0] = g_strdup(item->path);
1998 g_node_traverse(item->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
1999 imap_rename_folder_func, paths);
2001 if (is_dir_exist(old_cache_dir)) {
2002 new_cache_dir = folder_item_get_path(item);
2003 if (rename(old_cache_dir, new_cache_dir) < 0) {
2004 FILE_OP_ERROR(old_cache_dir, "rename");
2006 g_free(new_cache_dir);
2009 g_free(old_cache_dir);
2012 g_free(real_oldpath);
2013 g_free(real_newpath);
2018 static gint imap_remove_folder_real(Folder *folder, FolderItem *item)
2021 IMAPSession *session;
2025 g_return_val_if_fail(folder != NULL, -1);
2026 g_return_val_if_fail(item != NULL, -1);
2027 g_return_val_if_fail(item->path != NULL, -1);
2029 session = imap_session_get(folder);
2034 path = imap_get_real_path(IMAP_FOLDER(folder), item->path);
2036 ok = imap_cmd_delete(session, path);
2037 if (ok != IMAP_SUCCESS) {
2038 gchar *tmp = g_strdup_printf("%s%c", path,
2039 imap_get_path_separator(IMAP_FOLDER(folder), path));
2042 ok = imap_cmd_delete(session, path);
2045 if (ok != IMAP_SUCCESS) {
2046 log_warning(_("can't delete mailbox\n"));
2053 cache_dir = folder_item_get_path(item);
2054 if (is_dir_exist(cache_dir) && remove_dir_recursive(cache_dir) < 0)
2055 g_warning("can't remove directory '%s'\n", cache_dir);
2057 folder_item_remove(item);
2062 static gint imap_remove_folder(Folder *folder, FolderItem *item)
2066 g_return_val_if_fail(item != NULL, -1);
2067 g_return_val_if_fail(item->folder != NULL, -1);
2068 g_return_val_if_fail(item->node != NULL, -1);
2070 node = item->node->children;
2071 while (node != NULL) {
2073 if (imap_remove_folder(folder, FOLDER_ITEM(node->data)) < 0)
2077 debug_print("IMAP removing %s\n", item->path);
2079 if (imap_remove_all_msg(folder, item) < 0)
2081 return imap_remove_folder_real(folder, item);
2084 typedef struct _uncached_data {
2085 IMAPSession *session;
2087 MsgNumberList *numlist;
2093 static void *imap_get_uncached_messages_thread(void *data)
2095 uncached_data *stuff = (uncached_data *)data;
2096 IMAPSession *session = stuff->session;
2097 FolderItem *item = stuff->item;
2098 MsgNumberList *numlist = stuff->numlist;
2100 GSList *newlist = NULL;
2101 GSList *llast = NULL;
2102 GSList *seq_list, *cur;
2104 debug_print("uncached_messages\n");
2106 if (session == NULL || item == NULL || item->folder == NULL
2107 || FOLDER_CLASS(item->folder) != &imap_class) {
2112 seq_list = imap_get_lep_set_from_numlist(numlist);
2113 debug_print("get msgs info\n");
2114 for (cur = seq_list; cur != NULL; cur = g_slist_next(cur)) {
2115 struct mailimap_set * imapset;
2121 imapset = cur->data;
2123 r = imap_threaded_fetch_env(session->folder,
2124 imapset, &env_list);
2125 if (r != MAILIMAP_NO_ERROR)
2128 session_set_access_time(SESSION(session));
2131 for(i = 0 ; i < carray_count(env_list) ; i ++) {
2132 struct imap_fetch_env_info * info;
2135 info = carray_get(env_list, i);
2136 msginfo = imap_envelope_from_lep(info, item);
2137 if (msginfo == NULL)
2139 msginfo->folder = item;
2141 llast = newlist = g_slist_append(newlist, msginfo);
2143 llast = g_slist_append(llast, msginfo);
2144 llast = llast->next;
2149 imap_fetch_env_free(env_list);
2152 for (cur = seq_list; cur != NULL; cur = g_slist_next(cur)) {
2153 struct mailimap_set * imapset;
2155 imapset = cur->data;
2156 mailimap_set_free(imapset);
2159 session_set_access_time(SESSION(session));
2164 #define MAX_MSG_NUM 50
2166 static GSList *imap_get_uncached_messages(IMAPSession *session,
2168 MsgNumberList *numlist)
2170 GSList *result = NULL;
2172 uncached_data *data = g_new0(uncached_data, 1);
2177 data->total = g_slist_length(numlist);
2178 debug_print("messages list : %i\n", data->total);
2180 while (cur != NULL) {
2181 GSList * partial_result;
2189 while (count < MAX_MSG_NUM) {
2194 if (newlist == NULL)
2195 llast = newlist = g_slist_append(newlist, p);
2197 llast = g_slist_append(llast, p);
2198 llast = llast->next;
2208 data->session = session;
2210 data->numlist = newlist;
2213 if (prefs_common.work_offline &&
2214 !inc_offline_should_override(
2215 _("Sylpheed-Claws needs network access in order "
2216 "to access the IMAP server."))) {
2222 (GSList *)imap_get_uncached_messages_thread(data);
2224 statusbar_progress_all(data->cur,data->total, 1);
2226 g_slist_free(newlist);
2228 result = g_slist_concat(result, partial_result);
2232 statusbar_progress_all(0,0,0);
2233 statusbar_pop_all();
2238 static void imap_delete_all_cached_messages(FolderItem *item)
2242 g_return_if_fail(item != NULL);
2243 g_return_if_fail(item->folder != NULL);
2244 g_return_if_fail(FOLDER_CLASS(item->folder) == &imap_class);
2246 debug_print("Deleting all cached messages...\n");
2248 dir = folder_item_get_path(item);
2249 if (is_dir_exist(dir))
2250 remove_all_numbered_files(dir);
2253 debug_print("done.\n");
2256 static IMAPNameSpace *imap_find_namespace_from_list(GList *ns_list,
2259 IMAPNameSpace *namespace = NULL;
2260 gchar *tmp_path, *name;
2262 if (!path) path = "";
2264 for (; ns_list != NULL; ns_list = ns_list->next) {
2265 IMAPNameSpace *tmp_ns = ns_list->data;
2267 Xstrcat_a(tmp_path, path, "/", return namespace);
2268 Xstrdup_a(name, tmp_ns->name, return namespace);
2269 if (tmp_ns->separator && tmp_ns->separator != '/') {
2270 subst_char(tmp_path, tmp_ns->separator, '/');
2271 subst_char(name, tmp_ns->separator, '/');
2273 if (strncmp(tmp_path, name, strlen(name)) == 0)
2280 static IMAPNameSpace *imap_find_namespace(IMAPFolder *folder,
2283 IMAPNameSpace *namespace;
2285 g_return_val_if_fail(folder != NULL, NULL);
2287 namespace = imap_find_namespace_from_list(folder->ns_personal, path);
2288 if (namespace) return namespace;
2289 namespace = imap_find_namespace_from_list(folder->ns_others, path);
2290 if (namespace) return namespace;
2291 namespace = imap_find_namespace_from_list(folder->ns_shared, path);
2292 if (namespace) return namespace;
2297 gchar imap_get_path_separator_for_item(FolderItem *item)
2299 Folder *folder = NULL;
2300 IMAPFolder *imap_folder = NULL;
2303 folder = item->folder;
2308 imap_folder = IMAP_FOLDER(folder);
2313 return imap_get_path_separator(imap_folder, item->path);
2316 static gchar imap_get_path_separator(IMAPFolder *folder, const gchar *path)
2318 IMAPNameSpace *namespace;
2319 gchar separator = '/';
2320 IMAPSession *session = imap_session_get(FOLDER(folder));
2321 g_return_val_if_fail(session != NULL, '/');
2323 if (folder->last_seen_separator == 0) {
2325 int r = imap_threaded_list((Folder *)folder, "", "", &lep_list);
2326 if (r != MAILIMAP_NO_ERROR) {
2327 log_warning(_("LIST failed\n"));
2331 if (clist_count(lep_list) > 0) {
2332 clistiter * iter = clist_begin(lep_list);
2333 struct mailimap_mailbox_list * mb;
2334 mb = clist_content(iter);
2336 folder->last_seen_separator = mb->mb_delimiter;
2337 debug_print("got separator: %c\n", folder->last_seen_separator);
2339 mailimap_list_result_free(lep_list);
2342 if (folder->last_seen_separator != 0) {
2343 debug_print("using separator: %c\n", folder->last_seen_separator);
2344 return folder->last_seen_separator;
2347 namespace = imap_find_namespace(folder, path);
2348 if (namespace && namespace->separator)
2349 separator = namespace->separator;
2354 static gchar *imap_get_real_path(IMAPFolder *folder, const gchar *path)
2359 g_return_val_if_fail(folder != NULL, NULL);
2360 g_return_val_if_fail(path != NULL, NULL);
2362 real_path = imap_utf8_to_modified_utf7(path);
2363 separator = imap_get_path_separator(folder, path);
2364 imap_path_separator_subst(real_path, separator);
2369 static gint imap_set_message_flags(IMAPSession *session,
2370 MsgNumberList *numlist,
2378 seq_list = imap_get_lep_set_from_numlist(numlist);
2380 for(cur = seq_list ; cur != NULL ; cur = g_slist_next(cur)) {
2381 struct mailimap_set * imapset;
2383 imapset = cur->data;
2385 ok = imap_cmd_store(session, imapset,
2389 imap_lep_set_free(seq_list);
2391 return IMAP_SUCCESS;
2394 typedef struct _select_data {
2395 IMAPSession *session;
2400 guint32 *uid_validity;
2404 static gint imap_select(IMAPSession *session, IMAPFolder *folder,
2406 gint *exists, gint *recent, gint *unseen,
2407 guint32 *uid_validity, gboolean block)
2411 gint exists_, recent_, unseen_;
2412 guint32 uid_validity_;
2414 if (!exists && !recent && !unseen && !uid_validity) {
2415 if (session->mbox && strcmp(session->mbox, path) == 0)
2416 return IMAP_SUCCESS;
2425 uid_validity = &uid_validity_;
2427 g_free(session->mbox);
2428 session->mbox = NULL;
2430 real_path = imap_get_real_path(folder, path);
2432 ok = imap_cmd_select(session, real_path,
2433 exists, recent, unseen, uid_validity, block);
2434 if (ok != IMAP_SUCCESS)
2435 log_warning(_("can't select folder: %s\n"), real_path);
2437 session->mbox = g_strdup(path);
2438 session->folder_content_changed = FALSE;
2445 static gint imap_status(IMAPSession *session, IMAPFolder *folder,
2446 const gchar *path, IMAPFolderItem *item,
2448 guint32 *uid_next, guint32 *uid_validity,
2449 gint *unseen, gboolean block)
2453 struct mailimap_mailbox_data_status * data_status;
2458 real_path = imap_get_real_path(folder, path);
2472 r = imap_threaded_status(FOLDER(folder), real_path,
2473 &data_status, mask);
2476 if (r != MAILIMAP_NO_ERROR) {
2477 debug_print("status err %d\n", r);
2481 if (data_status->st_info_list == NULL) {
2482 mailimap_mailbox_data_status_free(data_status);
2483 debug_print("status->st_info_list == NULL\n");
2488 for(iter = clist_begin(data_status->st_info_list) ; iter != NULL ;
2489 iter = clist_next(iter)) {
2490 struct mailimap_status_info * info;
2492 info = clist_content(iter);
2493 switch (info->st_att) {
2494 case MAILIMAP_STATUS_ATT_MESSAGES:
2495 * messages = info->st_value;
2496 got_values |= 1 << 0;
2499 case MAILIMAP_STATUS_ATT_UIDNEXT:
2500 * uid_next = info->st_value;
2501 got_values |= 1 << 2;
2504 case MAILIMAP_STATUS_ATT_UIDVALIDITY:
2505 * uid_validity = info->st_value;
2506 got_values |= 1 << 3;
2509 case MAILIMAP_STATUS_ATT_UNSEEN:
2510 * unseen = info->st_value;
2511 got_values |= 1 << 4;
2515 mailimap_mailbox_data_status_free(data_status);
2517 if (got_values != mask) {
2518 debug_print("status: incomplete values received (%d)\n", got_values);
2521 return IMAP_SUCCESS;
2524 static void imap_free_capabilities(IMAPSession *session)
2526 slist_free_strings(session->capability);
2527 g_slist_free(session->capability);
2528 session->capability = NULL;
2531 /* low-level IMAP4rev1 commands */
2533 static gint imap_cmd_login(IMAPSession *session,
2534 const gchar *user, const gchar *pass,
2540 if (!strcmp(type, "LOGIN") && imap_has_capability(session, "LOGINDISABLED")) {
2541 gint ok = IMAP_ERROR;
2542 if (imap_has_capability(session, "STARTTLS")) {
2544 log_warning(_("Server requires TLS to log in.\n"));
2545 ok = imap_cmd_starttls(session);
2546 if (ok != IMAP_SUCCESS) {
2547 log_warning(_("Can't start TLS session.\n"));
2551 imap_free_capabilities(session);
2552 if (imap_get_capabilities(session) != MAILIMAP_NO_ERROR) {
2553 log_warning(_("Can't refresh capabilities.\n"));
2558 log_error(_("Connection to %s failed: "
2559 "server requires TLS, but Sylpheed-Claws "
2560 "has been compiled without OpenSSL "
2562 SESSION(session)->server);
2566 log_error(_("Server logins are disabled.\n"));
2571 log_print("IMAP4> Logging %s to %s using %s\n",
2573 SESSION(session)->server,
2575 r = imap_threaded_login(session->folder, user, pass, type);
2576 if (r != MAILIMAP_NO_ERROR) {
2577 log_print("IMAP4< Error logging in to %s\n",
2578 SESSION(session)->server);
2581 log_print("IMAP4< Login to %s successful\n",
2582 SESSION(session)->server);
2588 static gint imap_cmd_noop(IMAPSession *session)
2591 unsigned int exists;
2593 r = imap_threaded_noop(session->folder, &exists);
2594 if (r != MAILIMAP_NO_ERROR) {
2595 debug_print("noop err %d\n", r);
2598 session->exists = exists;
2599 session_set_access_time(SESSION(session));
2601 return IMAP_SUCCESS;
2605 static gint imap_cmd_starttls(IMAPSession *session)
2609 r = imap_threaded_starttls(session->folder,
2610 SESSION(session)->server, SESSION(session)->port);
2611 if (r != MAILIMAP_NO_ERROR) {
2612 debug_print("starttls err %d\n", r);
2615 return IMAP_SUCCESS;
2619 static gint imap_cmd_select(IMAPSession *session, const gchar *folder,
2620 gint *exists, gint *recent, gint *unseen,
2621 guint32 *uid_validity, gboolean block)
2625 r = imap_threaded_select(session->folder, folder,
2626 exists, recent, unseen, uid_validity);
2627 if (r != MAILIMAP_NO_ERROR) {
2628 debug_print("select err %d\n", r);
2631 return IMAP_SUCCESS;
2634 static gint imap_cmd_examine(IMAPSession *session, const gchar *folder,
2635 gint *exists, gint *recent, gint *unseen,
2636 guint32 *uid_validity, gboolean block)
2640 r = imap_threaded_examine(session->folder, folder,
2641 exists, recent, unseen, uid_validity);
2642 if (r != MAILIMAP_NO_ERROR) {
2643 debug_print("examine err %d\n", r);
2647 return IMAP_SUCCESS;
2650 static gint imap_cmd_create(IMAPSession *session, const gchar *folder)
2654 r = imap_threaded_create(session->folder, folder);
2655 if (r != MAILIMAP_NO_ERROR) {
2660 return IMAP_SUCCESS;
2663 static gint imap_cmd_rename(IMAPSession *session, const gchar *old_folder,
2664 const gchar *new_folder)
2668 r = imap_threaded_rename(session->folder, old_folder,
2670 if (r != MAILIMAP_NO_ERROR) {
2675 return IMAP_SUCCESS;
2678 static gint imap_cmd_delete(IMAPSession *session, const gchar *folder)
2683 r = imap_threaded_delete(session->folder, folder);
2684 if (r != MAILIMAP_NO_ERROR) {
2689 return IMAP_SUCCESS;
2692 typedef struct _fetch_data {
2693 IMAPSession *session;
2695 const gchar *filename;
2701 static void *imap_cmd_fetch_thread(void *data)
2703 fetch_data *stuff = (fetch_data *)data;
2704 IMAPSession *session = stuff->session;
2705 guint32 uid = stuff->uid;
2706 const gchar *filename = stuff->filename;
2710 r = imap_threaded_fetch_content(session->folder,
2714 r = imap_threaded_fetch_content(session->folder,
2717 if (r != MAILIMAP_NO_ERROR) {
2718 debug_print("fetch err %d\n", r);
2719 return GINT_TO_POINTER(IMAP_ERROR);
2721 return GINT_TO_POINTER(IMAP_SUCCESS);
2724 static gint imap_cmd_fetch(IMAPSession *session, guint32 uid,
2725 const gchar *filename, gboolean headers,
2728 fetch_data *data = g_new0(fetch_data, 1);
2731 data->session = session;
2733 data->filename = filename;
2734 data->headers = headers;
2737 if (prefs_common.work_offline &&
2738 !inc_offline_should_override(
2739 _("Sylpheed-Claws needs network access in order "
2740 "to access the IMAP server."))) {
2744 statusbar_print_all(_("Fetching message..."));
2745 result = GPOINTER_TO_INT(imap_cmd_fetch_thread(data));
2746 statusbar_pop_all();
2752 static gint imap_cmd_append(IMAPSession *session, const gchar *destfolder,
2753 const gchar *file, IMAPFlags flags,
2756 struct mailimap_flag_list * flag_list;
2759 g_return_val_if_fail(file != NULL, IMAP_ERROR);
2761 flag_list = imap_flag_to_lep(flags);
2762 r = imap_threaded_append(session->folder, destfolder,
2763 file, flag_list, (int *)new_uid);
2764 mailimap_flag_list_free(flag_list);
2766 if (r != MAILIMAP_NO_ERROR) {
2767 debug_print("append err %d\n", r);
2770 return IMAP_SUCCESS;
2773 static gint imap_cmd_copy(IMAPSession *session, struct mailimap_set * set,
2774 const gchar *destfolder, GRelation *uid_mapping,
2775 struct mailimap_set **source, struct mailimap_set **dest)
2779 g_return_val_if_fail(session != NULL, IMAP_ERROR);
2780 g_return_val_if_fail(set != NULL, IMAP_ERROR);
2781 g_return_val_if_fail(destfolder != NULL, IMAP_ERROR);
2783 r = imap_threaded_copy(session->folder, set, destfolder, source, dest);
2784 if (r != MAILIMAP_NO_ERROR) {
2789 return IMAP_SUCCESS;
2792 static gint imap_cmd_store(IMAPSession *session, struct mailimap_set * set,
2793 IMAPFlags flags, int do_add)
2796 struct mailimap_flag_list * flag_list;
2797 struct mailimap_store_att_flags * store_att_flags;
2799 flag_list = imap_flag_to_lep(flags);
2803 mailimap_store_att_flags_new_add_flags_silent(flag_list);
2806 mailimap_store_att_flags_new_remove_flags_silent(flag_list);
2808 r = imap_threaded_store(session->folder, set, store_att_flags);
2809 mailimap_store_att_flags_free(store_att_flags);
2810 if (r != MAILIMAP_NO_ERROR) {
2815 return IMAP_SUCCESS;
2818 static gint imap_cmd_expunge(IMAPSession *session)
2822 if (prefs_common.work_offline &&
2823 !inc_offline_should_override(
2824 _("Sylpheed-Claws needs network access in order "
2825 "to access the IMAP server."))) {
2829 r = imap_threaded_expunge(session->folder);
2830 if (r != MAILIMAP_NO_ERROR) {
2835 return IMAP_SUCCESS;
2838 static void imap_path_separator_subst(gchar *str, gchar separator)
2841 gboolean in_escape = FALSE;
2843 if (!separator || separator == '/') return;
2845 for (p = str; *p != '\0'; p++) {
2846 if (*p == '/' && !in_escape)
2848 else if (*p == '&' && *(p + 1) != '-' && !in_escape)
2850 else if (*p == '-' && in_escape)
2855 static gchar *imap_modified_utf7_to_utf8(const gchar *mutf7_str)
2857 static iconv_t cd = (iconv_t)-1;
2858 static gboolean iconv_ok = TRUE;
2861 size_t norm_utf7_len;
2863 gchar *to_str, *to_p;
2865 gboolean in_escape = FALSE;
2867 if (!iconv_ok) return g_strdup(mutf7_str);
2869 if (cd == (iconv_t)-1) {
2870 cd = iconv_open(CS_INTERNAL, CS_UTF_7);
2871 if (cd == (iconv_t)-1) {
2872 g_warning("iconv cannot convert UTF-7 to %s\n",
2875 return g_strdup(mutf7_str);
2879 /* modified UTF-7 to normal UTF-7 conversion */
2880 norm_utf7 = g_string_new(NULL);
2882 for (p = mutf7_str; *p != '\0'; p++) {
2883 /* replace: '&' -> '+',
2885 escaped ',' -> '/' */
2886 if (!in_escape && *p == '&') {
2887 if (*(p + 1) != '-') {
2888 g_string_append_c(norm_utf7, '+');
2891 g_string_append_c(norm_utf7, '&');
2894 } else if (in_escape && *p == ',') {
2895 g_string_append_c(norm_utf7, '/');
2896 } else if (in_escape && *p == '-') {
2897 g_string_append_c(norm_utf7, '-');
2900 g_string_append_c(norm_utf7, *p);
2904 norm_utf7_p = norm_utf7->str;
2905 norm_utf7_len = norm_utf7->len;
2906 to_len = strlen(mutf7_str) * 5;
2907 to_p = to_str = g_malloc(to_len + 1);
2909 if (iconv(cd, (ICONV_CONST gchar **)&norm_utf7_p, &norm_utf7_len,
2910 &to_p, &to_len) == -1) {
2911 g_warning(_("iconv cannot convert UTF-7 to %s\n"),
2912 conv_get_locale_charset_str());
2913 g_string_free(norm_utf7, TRUE);
2915 return g_strdup(mutf7_str);
2918 /* second iconv() call for flushing */
2919 iconv(cd, NULL, NULL, &to_p, &to_len);
2920 g_string_free(norm_utf7, TRUE);
2926 static gchar *imap_utf8_to_modified_utf7(const gchar *from)
2928 static iconv_t cd = (iconv_t)-1;
2929 static gboolean iconv_ok = TRUE;
2930 gchar *norm_utf7, *norm_utf7_p;
2931 size_t from_len, norm_utf7_len;
2933 gchar *from_tmp, *to, *p;
2934 gboolean in_escape = FALSE;
2936 if (!iconv_ok) return g_strdup(from);
2938 if (cd == (iconv_t)-1) {
2939 cd = iconv_open(CS_UTF_7, CS_INTERNAL);
2940 if (cd == (iconv_t)-1) {
2941 g_warning(_("iconv cannot convert %s to UTF-7\n"),
2944 return g_strdup(from);
2948 /* UTF-8 to normal UTF-7 conversion */
2949 Xstrdup_a(from_tmp, from, return g_strdup(from));
2950 from_len = strlen(from);
2951 norm_utf7_len = from_len * 5;
2952 Xalloca(norm_utf7, norm_utf7_len + 1, return g_strdup(from));
2953 norm_utf7_p = norm_utf7;
2955 #define IS_PRINT(ch) (isprint(ch) && IS_ASCII(ch))
2957 while (from_len > 0) {
2958 if (*from_tmp == '+') {
2959 *norm_utf7_p++ = '+';
2960 *norm_utf7_p++ = '-';
2964 } else if (IS_PRINT(*(guchar *)from_tmp)) {
2965 /* printable ascii char */
2966 *norm_utf7_p = *from_tmp;
2972 size_t conv_len = 0;
2974 /* unprintable char: convert to UTF-7 */
2976 while (!IS_PRINT(*(guchar *)p) && conv_len < from_len) {
2977 conv_len += g_utf8_skip[*(guchar *)p];
2978 p += g_utf8_skip[*(guchar *)p];
2981 from_len -= conv_len;
2982 if (iconv(cd, (ICONV_CONST gchar **)&from_tmp,
2984 &norm_utf7_p, &norm_utf7_len) == -1) {
2985 g_warning(_("iconv cannot convert UTF-8 to UTF-7\n"));
2986 return g_strdup(from);
2989 /* second iconv() call for flushing */
2990 iconv(cd, NULL, NULL, &norm_utf7_p, &norm_utf7_len);
2996 *norm_utf7_p = '\0';
2997 to_str = g_string_new(NULL);
2998 for (p = norm_utf7; p < norm_utf7_p; p++) {
2999 /* replace: '&' -> "&-",
3002 BASE64 '/' -> ',' */
3003 if (!in_escape && *p == '&') {
3004 g_string_append(to_str, "&-");
3005 } else if (!in_escape && *p == '+') {
3006 if (*(p + 1) == '-') {
3007 g_string_append_c(to_str, '+');
3010 g_string_append_c(to_str, '&');
3013 } else if (in_escape && *p == '/') {
3014 g_string_append_c(to_str, ',');
3015 } else if (in_escape && *p == '-') {
3016 g_string_append_c(to_str, '-');
3019 g_string_append_c(to_str, *p);
3025 g_string_append_c(to_str, '-');
3029 g_string_free(to_str, FALSE);
3034 static gboolean imap_rename_folder_func(GNode *node, gpointer data)
3036 FolderItem *item = node->data;
3037 gchar **paths = data;
3038 const gchar *oldpath = paths[0];
3039 const gchar *newpath = paths[1];
3041 gchar *new_itempath;
3044 oldpathlen = strlen(oldpath);
3045 if (strncmp(oldpath, item->path, oldpathlen) != 0) {
3046 g_warning("path doesn't match: %s, %s\n", oldpath, item->path);
3050 base = item->path + oldpathlen;
3051 while (*base == G_DIR_SEPARATOR) base++;
3053 new_itempath = g_strdup(newpath);
3055 new_itempath = g_strconcat(newpath, G_DIR_SEPARATOR_S, base,
3058 item->path = new_itempath;
3063 typedef struct _get_list_uid_data {
3065 IMAPSession *session;
3066 IMAPFolderItem *item;
3067 GSList **msgnum_list;
3069 } get_list_uid_data;
3071 static void *get_list_of_uids_thread(void *data)
3073 get_list_uid_data *stuff = (get_list_uid_data *)data;
3074 Folder *folder = stuff->folder;
3075 IMAPFolderItem *item = stuff->item;
3076 GSList **msgnum_list = stuff->msgnum_list;
3077 gint ok, nummsgs = 0, lastuid_old;
3078 IMAPSession *session;
3079 GSList *uidlist, *elem;
3080 clist * lep_uidlist;
3083 session = stuff->session;
3084 if (session == NULL) {
3086 return GINT_TO_POINTER(-1);
3088 /* no session locking here, it's already locked by caller */
3089 ok = imap_select(session, IMAP_FOLDER(folder), item->item.path,
3090 NULL, NULL, NULL, NULL, TRUE);
3091 if (ok != IMAP_SUCCESS) {
3093 return GINT_TO_POINTER(-1);
3098 r = imap_threaded_search(folder, IMAP_SEARCH_TYPE_SIMPLE, NULL,
3101 if (r == MAILIMAP_NO_ERROR) {
3102 GSList * fetchuid_list;
3105 imap_uid_list_from_lep(lep_uidlist);
3106 mailimap_search_result_free(lep_uidlist);
3108 uidlist = g_slist_concat(fetchuid_list, uidlist);
3111 GSList * fetchuid_list;
3112 carray * lep_uidtab;
3114 r = imap_threaded_fetch_uid(folder, item->lastuid + 1,
3116 if (r == MAILIMAP_NO_ERROR) {
3118 imap_uid_list_from_lep_tab(lep_uidtab);
3119 imap_fetch_uid_list_free(lep_uidtab);
3120 uidlist = g_slist_concat(fetchuid_list, uidlist);
3124 lastuid_old = item->lastuid;
3125 *msgnum_list = g_slist_copy(item->uid_list);
3126 nummsgs = g_slist_length(*msgnum_list);
3127 debug_print("Got %d uids from cache\n", g_slist_length(item->uid_list));
3129 for (elem = uidlist; elem != NULL; elem = g_slist_next(elem)) {
3132 msgnum = GPOINTER_TO_INT(elem->data);
3133 if (msgnum > lastuid_old) {
3134 *msgnum_list = g_slist_prepend(*msgnum_list, GINT_TO_POINTER(msgnum));
3135 item->uid_list = g_slist_prepend(item->uid_list, GINT_TO_POINTER(msgnum));
3138 if(msgnum > item->lastuid)
3139 item->lastuid = msgnum;
3142 g_slist_free(uidlist);
3144 return GINT_TO_POINTER(nummsgs);
3147 static gint get_list_of_uids(IMAPSession *session, Folder *folder, IMAPFolderItem *item, GSList **msgnum_list)
3150 get_list_uid_data *data = g_new0(get_list_uid_data, 1);
3152 data->folder = folder;
3154 data->msgnum_list = msgnum_list;
3155 data->session = session;
3156 if (prefs_common.work_offline &&
3157 !inc_offline_should_override(
3158 _("Sylpheed-Claws needs network access in order "
3159 "to access the IMAP server."))) {
3164 result = GPOINTER_TO_INT(get_list_of_uids_thread(data));
3170 gint imap_get_num_list(Folder *folder, FolderItem *_item, GSList **msgnum_list, gboolean *old_uids_valid)
3172 IMAPFolderItem *item = (IMAPFolderItem *)_item;
3173 IMAPSession *session;
3174 gint ok, nummsgs = 0, exists;
3175 guint32 uid_next = 0, uid_val = 0;
3176 GSList *uidlist = NULL;
3178 gboolean selected_folder;
3179 debug_print("get_num_list\n");
3181 g_return_val_if_fail(folder != NULL, -1);
3182 g_return_val_if_fail(item != NULL, -1);
3183 g_return_val_if_fail(item->item.path != NULL, -1);
3184 g_return_val_if_fail(FOLDER_CLASS(folder) == &imap_class, -1);
3185 g_return_val_if_fail(folder->account != NULL, -1);
3187 session = imap_session_get(folder);
3188 g_return_val_if_fail(session != NULL, -1);
3191 if (FOLDER_ITEM(item)->path)
3192 statusbar_print_all(_("Scanning folder %s%c%s ..."),
3193 FOLDER_ITEM(item)->folder->name,
3195 FOLDER_ITEM(item)->path);
3197 statusbar_print_all(_("Scanning folder %s ..."),
3198 FOLDER_ITEM(item)->folder->name);
3200 selected_folder = (session->mbox != NULL) &&
3201 (!strcmp(session->mbox, item->item.path));
3202 if (selected_folder && time(NULL) - item->use_cache < 2) {
3203 ok = imap_cmd_noop(session);
3204 if (ok != IMAP_SUCCESS) {
3205 debug_print("disconnected!\n");
3206 session = imap_reconnect_if_possible(folder, session);
3207 if (session == NULL) {
3208 statusbar_pop_all();
3213 exists = session->exists;
3215 uid_next = item->c_uid_next;
3216 uid_val = item->c_uid_validity;
3217 *old_uids_valid = TRUE;
3219 if (item->use_cache && time(NULL) - item->use_cache < 2) {
3220 exists = item->c_messages;
3221 uid_next = item->c_uid_next;
3222 uid_val = item->c_uid_validity;
3224 debug_print("using cache %d %d %d\n", exists, uid_next, uid_val);
3226 ok = imap_status(session, IMAP_FOLDER(folder), item->item.path, item,
3227 &exists, &uid_next, &uid_val, NULL, FALSE);
3229 item->item.last_num = uid_next - 1;
3231 item->use_cache = (time_t)0;
3232 if (ok != IMAP_SUCCESS) {
3233 statusbar_pop_all();
3237 if(item->item.mtime == uid_val)
3238 *old_uids_valid = TRUE;
3240 *old_uids_valid = FALSE;
3242 debug_print("Freeing imap uid cache (%d != %d)\n",
3243 (int)item->item.mtime, uid_val);
3245 g_slist_free(item->uid_list);
3246 item->uid_list = NULL;
3248 item->item.mtime = uid_val;
3250 imap_delete_all_cached_messages((FolderItem *)item);
3254 /* If old uid_next matches new uid_next we can be sure no message
3255 was added to the folder */
3256 debug_print("uid_next is %d and item->uid_next %d \n",
3257 uid_next, item->uid_next);
3258 if (uid_next == item->uid_next) {
3259 nummsgs = g_slist_length(item->uid_list);
3261 /* If number of messages is still the same we
3262 know our caches message numbers are still valid,
3263 otherwise if the number of messages has decrease
3264 we discard our cache to start a new scan to find
3265 out which numbers have been removed */
3266 if (exists == nummsgs) {
3267 debug_print("exists == nummsgs\n");
3268 *msgnum_list = g_slist_copy(item->uid_list);
3269 statusbar_pop_all();
3272 } else if (exists < nummsgs) {
3273 debug_print("Freeing imap uid cache");
3275 g_slist_free(item->uid_list);
3276 item->uid_list = NULL;
3281 *msgnum_list = NULL;
3282 statusbar_pop_all();
3287 nummsgs = get_list_of_uids(session, folder, item, &uidlist);
3290 statusbar_pop_all();
3295 if (nummsgs != exists) {
3296 /* Cache contains more messages then folder, we have cached
3297 an old UID of a message that was removed and new messages
3298 have been added too, otherwise the uid_next check would
3300 debug_print("Freeing imap uid cache");
3302 g_slist_free(item->uid_list);
3303 item->uid_list = NULL;
3305 g_slist_free(*msgnum_list);
3307 nummsgs = get_list_of_uids(session, folder, item, &uidlist);
3310 *msgnum_list = uidlist;
3312 dir = folder_item_get_path((FolderItem *)item);
3313 debug_print("removing old messages from %s\n", dir);
3314 remove_numbered_files_not_in_list(dir, *msgnum_list);
3317 item->uid_next = uid_next;
3319 debug_print("get_num_list - ok - %i\n", nummsgs);
3320 statusbar_pop_all();
3325 static MsgInfo *imap_parse_msg(const gchar *file, FolderItem *item)
3330 flags.perm_flags = MSG_NEW|MSG_UNREAD;
3331 flags.tmp_flags = 0;
3333 g_return_val_if_fail(item != NULL, NULL);
3334 g_return_val_if_fail(file != NULL, NULL);
3336 if (folder_has_parent_of_type(item, F_QUEUE)) {
3337 MSG_SET_TMP_FLAGS(flags, MSG_QUEUED);
3338 } else if (folder_has_parent_of_type(item, F_DRAFT)) {
3339 MSG_SET_TMP_FLAGS(flags, MSG_DRAFT);
3342 msginfo = procheader_parse_file(file, flags, FALSE, FALSE);
3343 if (!msginfo) return NULL;
3345 msginfo->plaintext_file = g_strdup(file);
3346 msginfo->folder = item;
3351 GSList *imap_get_msginfos(Folder *folder, FolderItem *item,
3352 GSList *msgnum_list)
3354 IMAPSession *session;
3355 MsgInfoList *ret = NULL;
3358 debug_print("get_msginfos\n");
3360 g_return_val_if_fail(folder != NULL, NULL);
3361 g_return_val_if_fail(item != NULL, NULL);
3362 g_return_val_if_fail(msgnum_list != NULL, NULL);
3364 session = imap_session_get(folder);
3365 g_return_val_if_fail(session != NULL, NULL);
3367 debug_print("IMAP getting msginfos\n");
3368 ok = imap_select(session, IMAP_FOLDER(folder), item->path,
3369 NULL, NULL, NULL, NULL, FALSE);
3370 if (ok != IMAP_SUCCESS) {
3374 if (!(folder_has_parent_of_type(item, F_DRAFT) ||
3375 folder_has_parent_of_type(item, F_QUEUE))) {
3376 ret = g_slist_concat(ret,
3377 imap_get_uncached_messages(session, item,
3380 MsgNumberList *sorted_list, *elem, *llast = NULL;
3381 gint startnum, lastnum;
3383 sorted_list = g_slist_sort(g_slist_copy(msgnum_list), g_int_compare);
3385 startnum = lastnum = GPOINTER_TO_INT(sorted_list->data);
3387 llast = g_slist_last(ret);
3388 for (elem = sorted_list;; elem = g_slist_next(elem)) {
3392 num = GPOINTER_TO_INT(elem->data);
3394 if (num > lastnum + 1 || elem == NULL) {
3396 for (i = startnum; i <= lastnum; ++i) {
3399 file = imap_fetch_msg(folder, item, i);
3401 MsgInfo *msginfo = imap_parse_msg(file, item);
3402 if (msginfo != NULL) {
3403 msginfo->msgnum = i;
3405 llast = ret = g_slist_append(ret, msginfo);
3407 llast = g_slist_append(llast, msginfo);
3408 llast = llast->next;
3413 session_set_access_time(SESSION(session));
3424 g_slist_free(sorted_list);
3430 MsgInfo *imap_get_msginfo(Folder *folder, FolderItem *item, gint uid)
3432 MsgInfo *msginfo = NULL;
3433 MsgInfoList *msginfolist;
3434 MsgNumberList numlist;
3436 numlist.next = NULL;
3437 numlist.data = GINT_TO_POINTER(uid);
3439 msginfolist = imap_get_msginfos(folder, item, &numlist);
3440 if (msginfolist != NULL) {
3441 msginfo = msginfolist->data;
3442 g_slist_free(msginfolist);
3448 gboolean imap_scan_required(Folder *folder, FolderItem *_item)
3450 IMAPSession *session;
3451 IMAPFolderItem *item = (IMAPFolderItem *)_item;
3452 gint ok, exists = 0, unseen = 0;
3453 guint32 uid_next = 0, uid_val = 0;
3454 gboolean selected_folder;
3456 g_return_val_if_fail(folder != NULL, FALSE);
3457 g_return_val_if_fail(item != NULL, FALSE);
3458 g_return_val_if_fail(item->item.folder != NULL, FALSE);
3459 g_return_val_if_fail(FOLDER_CLASS(item->item.folder) == &imap_class, FALSE);
3461 if (item->item.path == NULL)
3464 session = imap_session_get(folder);
3465 g_return_val_if_fail(session != NULL, FALSE);
3467 selected_folder = (session->mbox != NULL) &&
3468 (!strcmp(session->mbox, item->item.path));
3469 if (selected_folder && time(NULL) - item->use_cache < 2) {
3470 ok = imap_cmd_noop(session);
3471 if (ok != IMAP_SUCCESS) {
3472 debug_print("disconnected!\n");
3473 session = imap_reconnect_if_possible(folder, session);
3474 if (session == NULL)
3479 if (session->folder_content_changed
3480 || session->exists != item->item.total_msgs) {
3485 ok = imap_status(session, IMAP_FOLDER(folder), item->item.path, IMAP_FOLDER_ITEM(item),
3486 &exists, &uid_next, &uid_val, &unseen, FALSE);
3487 if (ok != IMAP_SUCCESS) {
3492 item->use_cache = time(NULL);
3493 item->c_messages = exists;
3494 item->c_uid_next = uid_next;
3495 item->c_uid_validity = uid_val;
3496 item->c_unseen = unseen;
3497 item->item.last_num = uid_next - 1;
3498 debug_print("uidnext %d, item->uid_next %d, exists %d, item->item.total_msgs %d\n",
3499 uid_next, item->uid_next, exists, item->item.total_msgs);
3500 if ((uid_next != item->uid_next) || (exists != item->item.total_msgs)
3501 || unseen != item->item.unread_msgs || uid_val != item->item.mtime) {
3510 void imap_change_flags(Folder *folder, FolderItem *item, MsgInfo *msginfo, MsgPermFlags newflags)
3512 IMAPSession *session;
3513 IMAPFlags flags_set = 0, flags_unset = 0;
3514 gint ok = IMAP_SUCCESS;
3515 MsgNumberList numlist;
3516 hashtable_data *ht_data = NULL;
3518 g_return_if_fail(folder != NULL);
3519 g_return_if_fail(folder->klass == &imap_class);
3520 g_return_if_fail(item != NULL);
3521 g_return_if_fail(item->folder == folder);
3522 g_return_if_fail(msginfo != NULL);
3523 g_return_if_fail(msginfo->folder == item);
3525 session = imap_session_get(folder);
3530 if (!MSG_IS_MARKED(msginfo->flags) && (newflags & MSG_MARKED))
3531 flags_set |= IMAP_FLAG_FLAGGED;
3532 if ( MSG_IS_MARKED(msginfo->flags) && !(newflags & MSG_MARKED))
3533 flags_unset |= IMAP_FLAG_FLAGGED;
3535 if (!MSG_IS_UNREAD(msginfo->flags) && (newflags & MSG_UNREAD))
3536 flags_unset |= IMAP_FLAG_SEEN;
3537 if ( MSG_IS_UNREAD(msginfo->flags) && !(newflags & MSG_UNREAD))
3538 flags_set |= IMAP_FLAG_SEEN;
3540 if (!MSG_IS_REPLIED(msginfo->flags) && (newflags & MSG_REPLIED))
3541 flags_set |= IMAP_FLAG_ANSWERED;
3542 if ( MSG_IS_REPLIED(msginfo->flags) && !(newflags & MSG_REPLIED))
3543 flags_unset |= IMAP_FLAG_ANSWERED;
3545 if (!MSG_IS_DELETED(msginfo->flags) && (newflags & MSG_DELETED))
3546 flags_set |= IMAP_FLAG_DELETED;
3547 if ( MSG_IS_DELETED(msginfo->flags) && !(newflags & MSG_DELETED))
3548 flags_unset |= IMAP_FLAG_DELETED;
3550 if (!flags_set && !flags_unset) {
3551 /* the changed flags were not translatable to IMAP-speak.
3552 * like MSG_POSTFILTERED, so just apply. */
3553 msginfo->flags.perm_flags = newflags;
3558 if ((ok = imap_select(session, IMAP_FOLDER(folder), msginfo->folder->path,
3559 NULL, NULL, NULL, NULL, FALSE)) != IMAP_SUCCESS) {
3563 numlist.next = NULL;
3564 numlist.data = GINT_TO_POINTER(msginfo->msgnum);
3566 if (IMAP_FOLDER_ITEM(item)->batching) {
3567 /* instead of performing an UID STORE command for each message change,
3568 * as a lot of them can change "together", we just fill in hashtables
3569 * and defer the treatment so that we're able to send only one
3572 debug_print("IMAP batch mode on, deferring flags change\n");
3574 ht_data = g_hash_table_lookup(IMAP_FOLDER_ITEM(item)->flags_set_table,
3575 GINT_TO_POINTER(flags_set));
3576 if (ht_data == NULL) {
3577 ht_data = g_new0(hashtable_data, 1);
3578 ht_data->session = session;
3579 ht_data->item = IMAP_FOLDER_ITEM(item);
3580 g_hash_table_insert(IMAP_FOLDER_ITEM(item)->flags_set_table,
3581 GINT_TO_POINTER(flags_set), ht_data);
3583 if (!g_slist_find(ht_data->msglist, GINT_TO_POINTER(msginfo->msgnum)))
3584 ht_data->msglist = g_slist_prepend(ht_data->msglist, GINT_TO_POINTER(msginfo->msgnum));
3587 ht_data = g_hash_table_lookup(IMAP_FOLDER_ITEM(item)->flags_unset_table,
3588 GINT_TO_POINTER(flags_unset));
3589 if (ht_data == NULL) {
3590 ht_data = g_new0(hashtable_data, 1);
3591 ht_data->session = session;
3592 ht_data->item = IMAP_FOLDER_ITEM(item);
3593 g_hash_table_insert(IMAP_FOLDER_ITEM(item)->flags_unset_table,
3594 GINT_TO_POINTER(flags_unset), ht_data);
3596 if (!g_slist_find(ht_data->msglist, GINT_TO_POINTER(msginfo->msgnum)))
3597 ht_data->msglist = g_slist_prepend(ht_data->msglist,
3598 GINT_TO_POINTER(msginfo->msgnum));
3601 debug_print("IMAP changing flags\n");
3603 ok = imap_set_message_flags(session, &numlist, flags_set, TRUE);
3604 if (ok != IMAP_SUCCESS) {
3611 ok = imap_set_message_flags(session, &numlist, flags_unset, FALSE);
3612 if (ok != IMAP_SUCCESS) {
3618 msginfo->flags.perm_flags = newflags;
3623 static gint imap_remove_msg(Folder *folder, FolderItem *item, gint uid)
3626 IMAPSession *session;
3628 MsgNumberList numlist;
3630 g_return_val_if_fail(folder != NULL, -1);
3631 g_return_val_if_fail(FOLDER_CLASS(folder) == &imap_class, -1);
3632 g_return_val_if_fail(item != NULL, -1);
3634 session = imap_session_get(folder);
3635 if (!session) return -1;
3637 ok = imap_select(session, IMAP_FOLDER(folder), item->path,
3638 NULL, NULL, NULL, NULL, FALSE);
3639 if (ok != IMAP_SUCCESS) {
3643 numlist.next = NULL;
3644 numlist.data = GINT_TO_POINTER(uid);
3646 ok = imap_set_message_flags
3647 (IMAP_SESSION(REMOTE_FOLDER(folder)->session),
3648 &numlist, IMAP_FLAG_DELETED, TRUE);
3649 if (ok != IMAP_SUCCESS) {
3650 log_warning(_("can't set deleted flags: %d\n"), uid);
3655 if (!session->uidplus) {
3656 ok = imap_cmd_expunge(session);
3660 uidstr = g_strdup_printf("%u", uid);
3661 ok = imap_cmd_expunge(session);
3664 if (ok != IMAP_SUCCESS) {
3665 log_warning(_("can't expunge\n"));
3670 IMAP_FOLDER_ITEM(item)->uid_list = g_slist_remove(
3671 IMAP_FOLDER_ITEM(item)->uid_list, numlist.data);
3672 dir = folder_item_get_path(item);
3673 if (is_dir_exist(dir))
3674 remove_numbered_files(dir, uid, uid);
3677 return IMAP_SUCCESS;
3680 static gint compare_msginfo(gconstpointer a, gconstpointer b)
3682 return ((MsgInfo *)a)->msgnum - ((MsgInfo *)b)->msgnum;
3685 static guint gslist_find_next_num(MsgNumberList **list, guint num)
3689 g_return_val_if_fail(list != NULL, -1);
3691 for (elem = *list; elem != NULL; elem = g_slist_next(elem))
3692 if (GPOINTER_TO_INT(elem->data) >= num)
3695 return elem != NULL ? GPOINTER_TO_INT(elem->data) : (gint)-1;
3699 * NEW and DELETED flags are not syncronized
3700 * - The NEW/RECENT flags in IMAP folders can not really be directly
3701 * modified by Sylpheed
3702 * - The DELETE/DELETED flag in IMAP and Sylpheed don't have the same
3703 * meaning, in IMAP it always removes the messages from the FolderItem
3704 * in Sylpheed it can mean to move the message to trash
3707 typedef struct _get_flags_data {
3710 MsgInfoList *msginfo_list;
3711 GRelation *msgflags;
3712 gboolean full_search;
3716 static /*gint*/ void *imap_get_flags_thread(void *data)
3718 get_flags_data *stuff = (get_flags_data *)data;
3719 Folder *folder = stuff->folder;
3720 FolderItem *item = stuff->item;
3721 MsgInfoList *msginfo_list = stuff->msginfo_list;
3722 GRelation *msgflags = stuff->msgflags;
3723 gboolean full_search = stuff->full_search;
3724 IMAPSession *session;
3725 GSList *sorted_list = NULL;
3726 GSList *unseen = NULL, *answered = NULL, *flagged = NULL, *deleted = NULL;
3727 GSList *p_unseen, *p_answered, *p_flagged, *p_deleted;
3729 GSList *seq_list, *cur;
3730 gboolean reverse_seen = FALSE;
3733 gint exists_cnt, unseen_cnt;
3734 gboolean selected_folder;
3736 if (folder == NULL || item == NULL) {
3738 return GINT_TO_POINTER(-1);
3741 session = imap_session_get(folder);
3742 if (session == NULL) {
3744 return GINT_TO_POINTER(-1);
3747 selected_folder = (session->mbox != NULL) &&
3748 (!strcmp(session->mbox, item->path));
3750 if (!selected_folder) {
3751 ok = imap_select(session, IMAP_FOLDER(folder), item->path,
3752 &exists_cnt, NULL, &unseen_cnt, NULL, TRUE);
3753 if (ok != IMAP_SUCCESS) {
3756 return GINT_TO_POINTER(-1);
3759 if (unseen_cnt > exists_cnt / 2)
3760 reverse_seen = TRUE;
3763 if (item->unread_msgs > item->total_msgs / 2)
3764 reverse_seen = TRUE;
3767 cmd_buf = g_string_new(NULL);
3769 sorted_list = g_slist_sort(g_slist_copy(msginfo_list), compare_msginfo);
3771 seq_list = imap_get_lep_set_from_msglist(msginfo_list);
3773 struct mailimap_set * set;
3774 set = mailimap_set_new_interval(1, 0);
3775 seq_list = g_slist_append(NULL, set);
3778 for (cur = seq_list; cur != NULL; cur = g_slist_next(cur)) {
3779 struct mailimap_set * imapset;
3780 clist * lep_uidlist;
3783 imapset = cur->data;
3785 r = imap_threaded_search(folder, IMAP_SEARCH_TYPE_SEEN,
3786 full_search ? NULL:imapset, &lep_uidlist);
3789 r = imap_threaded_search(folder,
3790 IMAP_SEARCH_TYPE_UNSEEN,
3791 full_search ? NULL:imapset, &lep_uidlist);
3793 if (r == MAILIMAP_NO_ERROR) {
3796 uidlist = imap_uid_list_from_lep(lep_uidlist);
3797 mailimap_search_result_free(lep_uidlist);
3799 unseen = g_slist_concat(unseen, uidlist);
3802 r = imap_threaded_search(folder, IMAP_SEARCH_TYPE_FLAGGED,
3803 full_search ? NULL:imapset, &lep_uidlist);
3804 if (r == MAILIMAP_NO_ERROR) {
3807 uidlist = imap_uid_list_from_lep(lep_uidlist);
3808 mailimap_search_result_free(lep_uidlist);
3810 flagged = g_slist_concat(flagged, uidlist);
3813 if (item->opened || item->processing_pending || item == folder->inbox) {
3814 r = imap_threaded_search(folder, IMAP_SEARCH_TYPE_ANSWERED,
3815 full_search ? NULL:imapset, &lep_uidlist);
3816 if (r == MAILIMAP_NO_ERROR) {
3819 uidlist = imap_uid_list_from_lep(lep_uidlist);
3820 mailimap_search_result_free(lep_uidlist);
3822 answered = g_slist_concat(answered, uidlist);
3825 r = imap_threaded_search(folder, IMAP_SEARCH_TYPE_DELETED,
3826 full_search ? NULL:imapset, &lep_uidlist);
3827 if (r == MAILIMAP_NO_ERROR) {
3830 uidlist = imap_uid_list_from_lep(lep_uidlist);
3831 mailimap_search_result_free(lep_uidlist);
3833 deleted = g_slist_concat(deleted, uidlist);
3839 p_answered = answered;
3840 p_flagged = flagged;
3841 p_deleted = deleted;
3843 for (elem = sorted_list; elem != NULL; elem = g_slist_next(elem)) {
3848 msginfo = (MsgInfo *) elem->data;
3849 flags = msginfo->flags.perm_flags;
3850 wasnew = (flags & MSG_NEW);
3851 if (item->opened || item->processing_pending || item == folder->inbox) {
3852 flags &= ~((reverse_seen ? 0 : MSG_UNREAD | MSG_NEW) | MSG_REPLIED | MSG_MARKED);
3854 flags &= ~((reverse_seen ? 0 : MSG_UNREAD | MSG_NEW | MSG_MARKED));
3857 flags |= MSG_UNREAD | (wasnew ? MSG_NEW : 0);
3858 if (gslist_find_next_num(&p_unseen, msginfo->msgnum) == msginfo->msgnum) {
3859 if (!reverse_seen) {
3860 flags |= MSG_UNREAD | (wasnew ? MSG_NEW : 0);
3862 flags &= ~(MSG_UNREAD | MSG_NEW);
3866 if (gslist_find_next_num(&p_flagged, msginfo->msgnum) == msginfo->msgnum)
3867 flags |= MSG_MARKED;
3869 flags &= ~MSG_MARKED;
3871 if (item->opened || item->processing_pending || item == folder->inbox) {
3872 if (gslist_find_next_num(&p_answered, msginfo->msgnum) == msginfo->msgnum)
3873 flags |= MSG_REPLIED;
3875 flags &= ~MSG_REPLIED;
3876 if (gslist_find_next_num(&p_deleted, msginfo->msgnum) == msginfo->msgnum)
3877 flags |= MSG_DELETED;
3879 flags &= ~MSG_DELETED;
3881 g_relation_insert(msgflags, msginfo, GINT_TO_POINTER(flags));
3884 imap_lep_set_free(seq_list);
3885 g_slist_free(flagged);
3886 g_slist_free(deleted);
3887 g_slist_free(answered);
3888 g_slist_free(unseen);
3889 g_slist_free(sorted_list);
3890 g_string_free(cmd_buf, TRUE);
3894 return GINT_TO_POINTER(0);
3897 static gint imap_get_flags(Folder *folder, FolderItem *item,
3898 MsgInfoList *msginfo_list, GRelation *msgflags)
3901 get_flags_data *data = g_new0(get_flags_data, 1);
3903 data->folder = folder;
3905 data->msginfo_list = msginfo_list;
3906 data->msgflags = msgflags;
3907 data->full_search = FALSE;
3909 GSList *tmp = NULL, *cur;
3911 if (prefs_common.work_offline &&
3912 !inc_offline_should_override(
3913 _("Sylpheed-Claws needs network access in order "
3914 "to access the IMAP server."))) {
3919 tmp = folder_item_get_msg_list(item);
3921 if (g_slist_length(tmp) <= g_slist_length(msginfo_list))
3922 data->full_search = TRUE;
3924 for (cur = tmp; cur; cur = cur->next)
3925 procmsg_msginfo_free((MsgInfo *)cur->data);
3929 result = GPOINTER_TO_INT(imap_get_flags_thread(data));
3936 static gboolean process_flags(gpointer key, gpointer value, gpointer user_data)
3938 gboolean flags_set = GPOINTER_TO_INT(user_data);
3939 gint flags_value = GPOINTER_TO_INT(key);
3940 hashtable_data *data = (hashtable_data *)value;
3941 IMAPFolderItem *_item = data->item;
3942 FolderItem *item = (FolderItem *)_item;
3943 gint ok = IMAP_ERROR;
3944 IMAPSession *session = imap_session_get(item->folder);
3946 data->msglist = g_slist_reverse(data->msglist);
3948 debug_print("IMAP %ssetting flags to %d for %d messages\n",
3951 g_slist_length(data->msglist));
3955 ok = imap_select(session, IMAP_FOLDER(item->folder), item->path,
3956 NULL, NULL, NULL, NULL, FALSE);
3958 if (ok == IMAP_SUCCESS) {
3959 imap_set_message_flags(data->session, data->msglist, flags_value, flags_set);
3961 g_warning("can't select mailbox %s\n", item->path);
3965 g_slist_free(data->msglist);
3970 static void process_hashtable(IMAPFolderItem *item)
3972 if (item->flags_set_table) {
3973 g_hash_table_foreach_remove(item->flags_set_table, process_flags, GINT_TO_POINTER(TRUE));
3974 g_hash_table_destroy(item->flags_set_table);
3975 item->flags_set_table = NULL;
3977 if (item->flags_unset_table) {
3978 g_hash_table_foreach_remove(item->flags_unset_table, process_flags, GINT_TO_POINTER(FALSE));
3979 g_hash_table_destroy(item->flags_unset_table);
3980 item->flags_unset_table = NULL;
3984 static void imap_set_batch (Folder *folder, FolderItem *_item, gboolean batch)
3986 IMAPFolderItem *item = (IMAPFolderItem *)_item;
3988 g_return_if_fail(item != NULL);
3990 if (item->batching == batch)
3994 item->batching = TRUE;
3995 debug_print("IMAP switching to batch mode\n");
3996 if (!item->flags_set_table) {
3997 item->flags_set_table = g_hash_table_new(NULL, g_direct_equal);
3999 if (!item->flags_unset_table) {
4000 item->flags_unset_table = g_hash_table_new(NULL, g_direct_equal);
4003 debug_print("IMAP switching away from batch mode\n");
4005 process_hashtable(item);
4006 item->batching = FALSE;
4012 /* data types conversion libetpan <-> sylpheed */
4016 #define ETPAN_IMAP_MB_MARKED 1
4017 #define ETPAN_IMAP_MB_UNMARKED 2
4018 #define ETPAN_IMAP_MB_NOSELECT 4
4019 #define ETPAN_IMAP_MB_NOINFERIORS 8
4021 static int imap_flags_to_flags(struct mailimap_mbx_list_flags * imap_flags)
4027 if (imap_flags->mbf_type == MAILIMAP_MBX_LIST_FLAGS_SFLAG) {
4028 switch (imap_flags->mbf_sflag) {
4029 case MAILIMAP_MBX_LIST_SFLAG_MARKED:
4030 flags |= ETPAN_IMAP_MB_MARKED;
4032 case MAILIMAP_MBX_LIST_SFLAG_NOSELECT:
4033 flags |= ETPAN_IMAP_MB_NOSELECT;
4035 case MAILIMAP_MBX_LIST_SFLAG_UNMARKED:
4036 flags |= ETPAN_IMAP_MB_UNMARKED;
4041 for(cur = clist_begin(imap_flags->mbf_oflags) ; cur != NULL ;
4042 cur = clist_next(cur)) {
4043 struct mailimap_mbx_list_oflag * oflag;
4045 oflag = clist_content(cur);
4047 switch (oflag->of_type) {
4048 case MAILIMAP_MBX_LIST_OFLAG_NOINFERIORS:
4049 flags |= ETPAN_IMAP_MB_NOINFERIORS;
4057 static GSList * imap_list_from_lep(IMAPFolder * folder,
4058 clist * list, const gchar * real_path, gboolean all)
4061 GSList * item_list = NULL, *llast = NULL;
4063 for(iter = clist_begin(list) ; iter != NULL ;
4064 iter = clist_next(iter)) {
4065 struct mailimap_mailbox_list * mb;
4073 FolderItem *new_item;
4075 mb = clist_content(iter);
4081 if (mb->mb_flag != NULL)
4082 flags = imap_flags_to_flags(mb->mb_flag);
4084 delimiter = mb->mb_delimiter;
4087 dup_name = strdup(name);
4088 if (delimiter != '\0')
4089 subst_char(dup_name, delimiter, '/');
4091 base = g_path_get_basename(dup_name);
4092 if (base[0] == '.') {
4098 if (!all && strcmp(dup_name, real_path) == 0) {
4104 if (!all && dup_name[strlen(dup_name)-1] == '/') {
4110 loc_name = imap_modified_utf7_to_utf8(base);
4111 loc_path = imap_modified_utf7_to_utf8(dup_name);
4113 new_item = folder_item_new(FOLDER(folder), loc_name, loc_path);
4114 if ((flags & ETPAN_IMAP_MB_NOINFERIORS) != 0)
4115 new_item->no_sub = TRUE;
4116 if (strcmp(dup_name, "INBOX") != 0 &&
4117 ((flags & ETPAN_IMAP_MB_NOSELECT) != 0))
4118 new_item->no_select = TRUE;
4120 if (item_list == NULL)
4121 llast = item_list = g_slist_append(item_list, new_item);
4123 llast = g_slist_append(llast, new_item);
4124 llast = llast->next;
4126 debug_print("folder '%s' found.\n", loc_path);
4137 static GSList * imap_get_lep_set_from_numlist(MsgNumberList *numlist)
4139 GSList *sorted_list, *cur;
4140 guint first, last, next;
4141 GSList *ret_list = NULL, *llast = NULL;
4143 struct mailimap_set * current_set;
4144 unsigned int item_count;
4146 if (numlist == NULL)
4150 current_set = mailimap_set_new_empty();
4152 sorted_list = g_slist_copy(numlist);
4153 sorted_list = g_slist_sort(sorted_list, g_int_compare);
4155 first = GPOINTER_TO_INT(sorted_list->data);
4158 for (cur = sorted_list; cur != NULL; cur = g_slist_next(cur)) {
4159 if (GPOINTER_TO_INT(cur->data) == 0)
4164 last = GPOINTER_TO_INT(cur->data);
4166 next = GPOINTER_TO_INT(cur->next->data);
4170 if (last + 1 != next || next == 0) {
4172 struct mailimap_set_item * item;
4173 item = mailimap_set_item_new(first, last);
4174 mailimap_set_add(current_set, item);
4179 if (count >= IMAP_SET_MAX_COUNT) {
4180 if (ret_list == NULL)
4181 llast = ret_list = g_slist_append(ret_list,
4184 llast = g_slist_append(llast, current_set);
4185 llast = llast->next;
4187 current_set = mailimap_set_new_empty();
4194 if (clist_count(current_set->set_list) > 0) {
4195 ret_list = g_slist_append(ret_list,
4199 g_slist_free(sorted_list);
4204 static GSList * imap_get_lep_set_from_msglist(MsgInfoList *msglist)
4206 MsgNumberList *numlist = NULL;
4210 for (cur = msglist; cur != NULL; cur = g_slist_next(cur)) {
4211 MsgInfo *msginfo = (MsgInfo *) cur->data;
4213 numlist = g_slist_prepend(numlist, GINT_TO_POINTER(msginfo->msgnum));
4215 numlist = g_slist_reverse(numlist);
4216 seq_list = imap_get_lep_set_from_numlist(numlist);
4217 g_slist_free(numlist);
4222 static GSList * imap_uid_list_from_lep(clist * list)
4229 for(iter = clist_begin(list) ; iter != NULL ;
4230 iter = clist_next(iter)) {
4233 puid = clist_content(iter);
4234 result = g_slist_prepend(result, GINT_TO_POINTER(* puid));
4237 result = g_slist_reverse(result);
4241 static GSList * imap_uid_list_from_lep_tab(carray * list)
4248 for(i = 0 ; i < carray_count(list) ; i ++) {
4251 puid = carray_get(list, i);
4252 result = g_slist_prepend(result, GINT_TO_POINTER(* puid));
4254 result = g_slist_reverse(result);
4258 static MsgInfo *imap_envelope_from_lep(struct imap_fetch_env_info * info,
4261 MsgInfo *msginfo = NULL;
4264 MsgFlags flags = {0, 0};
4266 if (info->headers == NULL)
4269 MSG_SET_TMP_FLAGS(flags, MSG_IMAP);
4270 if (folder_has_parent_of_type(item, F_QUEUE)) {
4271 MSG_SET_TMP_FLAGS(flags, MSG_QUEUED);
4272 } else if (folder_has_parent_of_type(item, F_DRAFT)) {
4273 MSG_SET_TMP_FLAGS(flags, MSG_DRAFT);
4275 flags.perm_flags = info->flags;
4279 msginfo = procheader_parse_str(info->headers, flags, FALSE, FALSE);
4282 msginfo->msgnum = uid;
4283 msginfo->size = size;
4289 static void imap_lep_set_free(GSList *seq_list)
4293 for(cur = seq_list ; cur != NULL ; cur = g_slist_next(cur)) {
4294 struct mailimap_set * imapset;
4296 imapset = cur->data;
4297 mailimap_set_free(imapset);
4299 g_slist_free(seq_list);
4302 static struct mailimap_flag_list * imap_flag_to_lep(IMAPFlags flags)
4304 struct mailimap_flag_list * flag_list;
4306 flag_list = mailimap_flag_list_new_empty();
4308 if (IMAP_IS_SEEN(flags))
4309 mailimap_flag_list_add(flag_list,
4310 mailimap_flag_new_seen());
4311 if (IMAP_IS_ANSWERED(flags))
4312 mailimap_flag_list_add(flag_list,
4313 mailimap_flag_new_answered());
4314 if (IMAP_IS_FLAGGED(flags))
4315 mailimap_flag_list_add(flag_list,
4316 mailimap_flag_new_flagged());
4317 if (IMAP_IS_DELETED(flags))
4318 mailimap_flag_list_add(flag_list,
4319 mailimap_flag_new_deleted());
4320 if (IMAP_IS_DRAFT(flags))
4321 mailimap_flag_list_add(flag_list,
4322 mailimap_flag_new_draft());
4327 guint imap_folder_get_refcnt(Folder *folder)
4329 return ((IMAPFolder *)folder)->refcnt;
4332 void imap_folder_ref(Folder *folder)
4334 ((IMAPFolder *)folder)->refcnt++;
4337 void imap_disconnect_all(void)
4340 for (list = account_get_list(); list != NULL; list = list->next) {
4341 PrefsAccount *account = list->data;
4342 if (account->protocol == A_IMAP4) {
4343 RemoteFolder *folder = (RemoteFolder *)account->folder;
4344 if (folder && folder->session) {
4345 IMAPSession *session = (IMAPSession *)folder->session;
4346 imap_threaded_disconnect(FOLDER(folder));
4347 SESSION(session)->state = SESSION_DISCONNECTED;
4348 session_destroy(SESSION(session));
4349 folder->session = NULL;
4355 void imap_folder_unref(Folder *folder)
4357 if (((IMAPFolder *)folder)->refcnt > 0)
4358 ((IMAPFolder *)folder)->refcnt--;
4361 #else /* HAVE_LIBETPAN */
4363 static FolderClass imap_class;
4365 static XMLTag *imap_item_get_xml(Folder *folder, FolderItem *item);
4366 static void imap_item_set_xml(Folder *folder, FolderItem *item, XMLTag *tag);
4368 static Folder *imap_folder_new (const gchar *name,
4371 static gboolean missing_imap_warning = TRUE;
4372 if (missing_imap_warning) {
4373 missing_imap_warning = FALSE;
4375 _("You have one or more IMAP accounts "
4376 "defined. However this version of "
4377 "Sylpheed-Claws has been built without "
4378 "IMAP support; your IMAP account(s) are "
4380 "You probably need to "
4381 "install libetpan and recompile "
4382 "Sylpheed-Claws."));
4386 static gint imap_create_tree (Folder *folder)
4390 static FolderItem *imap_create_folder (Folder *folder,
4396 static gint imap_rename_folder (Folder *folder,
4403 gchar imap_get_path_separator_for_item(FolderItem *item)
4408 FolderClass *imap_get_class(void)
4410 if (imap_class.idstr == NULL) {
4411 imap_class.type = F_IMAP;
4412 imap_class.idstr = "imap";
4413 imap_class.uistr = "IMAP4";
4415 imap_class.new_folder = imap_folder_new;
4416 imap_class.create_tree = imap_create_tree;
4417 imap_class.create_folder = imap_create_folder;
4418 imap_class.rename_folder = imap_rename_folder;
4420 imap_class.set_xml = folder_set_xml;
4421 imap_class.get_xml = folder_get_xml;
4422 imap_class.item_set_xml = imap_item_set_xml;
4423 imap_class.item_get_xml = imap_item_get_xml;
4424 /* nothing implemented */
4430 void imap_disconnect_all(void)
4436 void imap_synchronise(FolderItem *item)
4438 imap_gtk_synchronise(item);
4441 static void imap_item_set_xml(Folder *folder, FolderItem *item, XMLTag *tag)
4443 #ifdef HAVE_LIBETPAN
4446 folder_item_set_xml(folder, item, tag);
4448 #ifdef HAVE_LIBETPAN
4449 for (cur = tag->attr; cur != NULL; cur = g_list_next(cur)) {
4450 XMLAttr *attr = (XMLAttr *) cur->data;
4452 if (!attr || !attr->name || !attr->value) continue;
4453 if (!strcmp(attr->name, "uidnext"))
4454 IMAP_FOLDER_ITEM(item)->uid_next = atoi(attr->value);
4459 static XMLTag *imap_item_get_xml(Folder *folder, FolderItem *item)
4463 tag = folder_item_get_xml(folder, item);
4465 #ifdef HAVE_LIBETPAN
4466 xml_tag_add_attr(tag, xml_attr_new_int("uidnext",
4467 IMAP_FOLDER_ITEM(item)->uid_next));