2 * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2005 Hiroyuki Yamamoto
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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
27 #include <glib/gi18n.h>
50 #include "procheader.h"
51 #include "prefs_account.h"
56 #include "prefs_common.h"
57 #include "inputdialog.h"
59 #include "remotefolder.h"
60 #include "alertpanel.h"
62 #include "statusbar.h"
69 typedef struct _IMAPFolder IMAPFolder;
70 typedef struct _IMAPSession IMAPSession;
71 typedef struct _IMAPNameSpace IMAPNameSpace;
72 typedef struct _IMAPFolderItem IMAPFolderItem;
74 #include "prefs_account.h"
76 #define IMAP_FOLDER(obj) ((IMAPFolder *)obj)
77 #define IMAP_FOLDER_ITEM(obj) ((IMAPFolderItem *)obj)
78 #define IMAP_SESSION(obj) ((IMAPSession *)obj)
84 /* list of IMAPNameSpace */
94 gboolean authenticated;
103 gboolean folder_content_changed;
107 struct _IMAPNameSpace
113 #define IMAP_SUCCESS 0
114 #define IMAP_SOCKET 2
115 #define IMAP_AUTHFAIL 3
116 #define IMAP_PROTOCOL 4
117 #define IMAP_SYNTAX 5
121 #define IMAPBUFSIZE 8192
125 IMAP_FLAG_SEEN = 1 << 0,
126 IMAP_FLAG_ANSWERED = 1 << 1,
127 IMAP_FLAG_FLAGGED = 1 << 2,
128 IMAP_FLAG_DELETED = 1 << 3,
129 IMAP_FLAG_DRAFT = 1 << 4
132 #define IMAP_IS_SEEN(flags) ((flags & IMAP_FLAG_SEEN) != 0)
133 #define IMAP_IS_ANSWERED(flags) ((flags & IMAP_FLAG_ANSWERED) != 0)
134 #define IMAP_IS_FLAGGED(flags) ((flags & IMAP_FLAG_FLAGGED) != 0)
135 #define IMAP_IS_DELETED(flags) ((flags & IMAP_FLAG_DELETED) != 0)
136 #define IMAP_IS_DRAFT(flags) ((flags & IMAP_FLAG_DRAFT) != 0)
139 #define IMAP4_PORT 143
141 #define IMAPS_PORT 993
144 #define IMAP_CMD_LIMIT 1000
146 #define QUOTE_IF_REQUIRED(out, str) \
148 if (*str != '"' && strpbrk(str, " \t(){}[]%*\\") != NULL) { \
152 len = strlen(str) + 3; \
153 Xalloca(__tmp, len, return IMAP_ERROR); \
154 g_snprintf(__tmp, len, "\"%s\"", str); \
157 Xstrdup_a(out, str, return IMAP_ERROR); \
161 typedef gchar * IMAPSet;
163 struct _IMAPFolderItem
173 static void imap_folder_init (Folder *folder,
177 static Folder *imap_folder_new (const gchar *name,
179 static void imap_folder_destroy (Folder *folder);
181 static IMAPSession *imap_session_new (const PrefsAccount *account);
182 static void imap_session_authenticate(IMAPSession *session,
183 const PrefsAccount *account);
184 static void imap_session_destroy (Session *session);
186 static gchar *imap_fetch_msg (Folder *folder,
189 static gchar *imap_fetch_msg_full (Folder *folder,
194 static gint imap_add_msg (Folder *folder,
198 static gint imap_add_msgs (Folder *folder,
201 GRelation *relation);
203 static gint imap_copy_msg (Folder *folder,
206 static gint imap_copy_msgs (Folder *folder,
208 MsgInfoList *msglist,
209 GRelation *relation);
211 static gint imap_remove_msg (Folder *folder,
214 static gint imap_remove_msgs (Folder *folder,
216 MsgInfoList *msglist,
217 GRelation *relation);
218 static gint imap_remove_all_msg (Folder *folder,
221 static gboolean imap_is_msg_changed (Folder *folder,
225 static gint imap_close (Folder *folder,
228 static gint imap_scan_tree (Folder *folder);
230 static gint imap_create_tree (Folder *folder);
232 static FolderItem *imap_create_folder (Folder *folder,
235 static gint imap_rename_folder (Folder *folder,
238 static gint imap_remove_folder (Folder *folder,
241 static FolderItem *imap_folder_item_new (Folder *folder);
242 static void imap_folder_item_destroy (Folder *folder,
245 static IMAPSession *imap_session_get (Folder *folder);
247 static gint imap_greeting (IMAPSession *session);
248 static gint imap_auth (IMAPSession *session,
253 static gint imap_scan_tree_recursive (IMAPSession *session,
255 static GSList *imap_parse_list (IMAPFolder *folder,
256 IMAPSession *session,
257 const gchar *real_path,
260 static void imap_create_missing_folders (Folder *folder);
261 static FolderItem *imap_create_special_folder
263 SpecialFolderItemType stype,
266 static gint imap_do_copy_msgs (Folder *folder,
268 MsgInfoList *msglist,
269 GRelation *relation);
271 static void imap_delete_all_cached_messages (FolderItem *item);
272 static void imap_set_batch (Folder *folder,
276 static SockInfo *imap_open (const gchar *server,
280 static SockInfo *imap_open (const gchar *server,
285 static SockInfo *imap_open_tunnel(const gchar *server,
286 const gchar *tunnelcmd,
289 static SockInfo *imap_open_tunnel(const gchar *server,
290 const gchar *tunnelcmd);
294 static SockInfo *imap_init_sock(SockInfo *sock, SSLType ssl_type);
296 static SockInfo *imap_init_sock(SockInfo *sock);
299 static gchar *imap_get_flag_str (IMAPFlags flags);
300 static gint imap_set_message_flags (IMAPSession *session,
301 MsgNumberList *numlist,
304 static gint imap_select (IMAPSession *session,
310 guint32 *uid_validity,
312 static gint imap_status (IMAPSession *session,
318 guint32 *uid_validity,
322 static void imap_parse_namespace (IMAPSession *session,
324 static void imap_get_namespace_by_list (IMAPSession *session,
326 static IMAPNameSpace *imap_find_namespace (IMAPFolder *folder,
328 static gchar imap_get_path_separator (IMAPFolder *folder,
330 static gchar *imap_get_real_path (IMAPFolder *folder,
333 static gchar *imap_parse_atom (SockInfo *sock,
338 static MsgFlags imap_parse_flags (const gchar *flag_str);
339 static MsgInfo *imap_parse_envelope (SockInfo *sock,
343 static gboolean imap_has_capability (IMAPSession *session,
345 static void imap_free_capabilities (IMAPSession *session);
347 /* low-level IMAP4rev1 commands */
348 static gint imap_cmd_authenticate
349 (IMAPSession *session,
353 static gint imap_cmd_login (IMAPSession *session,
356 static gint imap_cmd_logout (IMAPSession *session);
357 static gint imap_cmd_noop (IMAPSession *session);
359 static gint imap_cmd_starttls (IMAPSession *session);
361 static gint imap_cmd_namespace (IMAPSession *session,
363 static gint imap_cmd_list (IMAPSession *session,
365 const gchar *mailbox,
367 static gint imap_cmd_do_select (IMAPSession *session,
373 guint32 *uid_validity,
375 static gint imap_cmd_select (IMAPSession *session,
380 guint32 *uid_validity,
382 static gint imap_cmd_examine (IMAPSession *session,
387 guint32 *uid_validity,
389 static gint imap_cmd_create (IMAPSession *sock,
390 const gchar *folder);
391 static gint imap_cmd_rename (IMAPSession *sock,
392 const gchar *oldfolder,
393 const gchar *newfolder);
394 static gint imap_cmd_delete (IMAPSession *session,
395 const gchar *folder);
396 static gint imap_cmd_envelope (IMAPSession *session,
398 static gint imap_cmd_fetch (IMAPSession *sock,
400 const gchar *filename,
403 static gint imap_cmd_append (IMAPSession *session,
404 const gchar *destfolder,
408 static gint imap_cmd_copy (IMAPSession *session,
409 const gchar *seq_set,
410 const gchar *destfolder,
411 GRelation *uid_mapping);
412 static gint imap_cmd_store (IMAPSession *session,
415 static gint imap_cmd_expunge (IMAPSession *session,
417 static gint imap_cmd_close (IMAPSession *session);
419 static gint imap_cmd_ok (IMAPSession *session,
421 static gint imap_cmd_ok_block (IMAPSession *session,
423 static gint imap_cmd_ok_with_block
424 (IMAPSession *session,
427 static void imap_gen_send (IMAPSession *session,
428 const gchar *format, ...);
429 static gint imap_gen_recv (IMAPSession *session,
431 static gint imap_gen_recv_block (IMAPSession *session,
433 static gint imap_gen_recv_with_block
434 (IMAPSession *session,
438 /* misc utility functions */
439 static gchar *strchr_cpy (const gchar *src,
443 static gchar *get_quoted (const gchar *src,
447 static gchar *search_array_contain_str (GPtrArray *array,
449 static gchar *search_array_str (GPtrArray *array,
451 static void imap_path_separator_subst (gchar *str,
454 static gchar *imap_utf8_to_modified_utf7 (const gchar *from);
455 static gchar *imap_modified_utf7_to_utf8 (const gchar *mutf7_str);
457 static GSList *imap_get_seq_set_from_numlist (MsgNumberList *msglist);
458 static GSList *imap_get_seq_set_from_msglist (MsgInfoList *msglist);
459 static void imap_seq_set_free (GSList *seq_list);
461 static gboolean imap_rename_folder_func (GNode *node,
463 static gint imap_get_num_list (Folder *folder,
466 gboolean *old_uids_valid);
467 static GSList *imap_get_msginfos (Folder *folder,
469 GSList *msgnum_list);
470 static MsgInfo *imap_get_msginfo (Folder *folder,
473 static gboolean imap_scan_required (Folder *folder,
475 static void imap_change_flags (Folder *folder,
478 MsgPermFlags newflags);
479 static gint imap_get_flags (Folder *folder,
481 MsgInfoList *msglist,
482 GRelation *msgflags);
483 static gchar *imap_folder_get_path (Folder *folder);
484 static gchar *imap_item_get_path (Folder *folder,
486 static MsgInfo *imap_parse_msg(const gchar *file, FolderItem *item);
487 static GHashTable *flags_set_table = NULL;
488 static GHashTable *flags_unset_table = NULL;
489 typedef struct _hashtable_data {
490 IMAPSession *session;
494 static FolderClass imap_class;
496 typedef struct _thread_data {
507 void *imap_getline_thread(void *data)
509 thread_data *td = (thread_data *)data;
512 line = sock_getline(td->sock);
520 /* imap_getline just wraps sock_getline inside a thread,
521 * performing gtk updates so that the interface isn't frozen.
523 static gchar *imap_getline(SockInfo *sock)
525 #if (defined USE_PTHREAD && defined __GLIBC__ && (__GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 3)))
526 thread_data *td = g_new0(thread_data, 1);
532 if (pthread_create(&pt, PTHREAD_CREATE_JOINABLE,
533 imap_getline_thread, td) != 0) {
535 return sock_getline(sock);
538 debug_print("+++waiting for imap_getline_thread...\n");
540 /* don't let the interface freeze while waiting */
543 debug_print("---imap_getline_thread done\n");
545 /* get the thread's return value and clean its resources */
546 pthread_join(pt, (void *)&line);
551 return sock_getline(sock);
555 FolderClass *imap_get_class(void)
557 if (imap_class.idstr == NULL) {
558 imap_class.type = F_IMAP;
559 imap_class.idstr = "imap";
560 imap_class.uistr = "IMAP4";
562 /* Folder functions */
563 imap_class.new_folder = imap_folder_new;
564 imap_class.destroy_folder = imap_folder_destroy;
565 imap_class.scan_tree = imap_scan_tree;
566 imap_class.create_tree = imap_create_tree;
568 /* FolderItem functions */
569 imap_class.item_new = imap_folder_item_new;
570 imap_class.item_destroy = imap_folder_item_destroy;
571 imap_class.item_get_path = imap_item_get_path;
572 imap_class.create_folder = imap_create_folder;
573 imap_class.rename_folder = imap_rename_folder;
574 imap_class.remove_folder = imap_remove_folder;
575 imap_class.close = imap_close;
576 imap_class.get_num_list = imap_get_num_list;
577 imap_class.scan_required = imap_scan_required;
579 /* Message functions */
580 imap_class.get_msginfo = imap_get_msginfo;
581 imap_class.get_msginfos = imap_get_msginfos;
582 imap_class.fetch_msg = imap_fetch_msg;
583 imap_class.fetch_msg_full = imap_fetch_msg_full;
584 imap_class.add_msg = imap_add_msg;
585 imap_class.add_msgs = imap_add_msgs;
586 imap_class.copy_msg = imap_copy_msg;
587 imap_class.copy_msgs = imap_copy_msgs;
588 imap_class.remove_msg = imap_remove_msg;
589 imap_class.remove_msgs = imap_remove_msgs;
590 imap_class.remove_all_msg = imap_remove_all_msg;
591 imap_class.is_msg_changed = imap_is_msg_changed;
592 imap_class.change_flags = imap_change_flags;
593 imap_class.get_flags = imap_get_flags;
594 imap_class.set_batch = imap_set_batch;
600 static gchar *get_seq_set_from_seq_list(GSList *seq_list)
606 for (cur = seq_list; cur != NULL; cur = cur->next) {
607 tmp = val?g_strdup(val):NULL;
609 val = g_strconcat(tmp?tmp:"", tmp?",":"",(gchar *)cur->data,
617 static Folder *imap_folder_new(const gchar *name, const gchar *path)
621 folder = (Folder *)g_new0(IMAPFolder, 1);
622 folder->klass = &imap_class;
623 imap_folder_init(folder, name, path);
628 static void imap_folder_destroy(Folder *folder)
632 dir = imap_folder_get_path(folder);
633 if (is_dir_exist(dir))
634 remove_dir_recursive(dir);
637 folder_remote_folder_destroy(REMOTE_FOLDER(folder));
640 static void imap_folder_init(Folder *folder, const gchar *name,
643 folder_remote_folder_init((Folder *)folder, name, path);
646 static FolderItem *imap_folder_item_new(Folder *folder)
648 IMAPFolderItem *item;
650 item = g_new0(IMAPFolderItem, 1);
653 item->uid_list = NULL;
655 return (FolderItem *)item;
658 static void imap_folder_item_destroy(Folder *folder, FolderItem *_item)
660 IMAPFolderItem *item = (IMAPFolderItem *)_item;
662 g_return_if_fail(item != NULL);
663 g_slist_free(item->uid_list);
668 static gboolean imap_reset_uid_lists_func(GNode *node, gpointer data)
670 IMAPFolderItem *item = (IMAPFolderItem *)node->data;
674 g_slist_free(item->uid_list);
675 item->uid_list = NULL;
680 static void imap_reset_uid_lists(Folder *folder)
682 if(folder->node == NULL)
685 /* Destroy all uid lists and rest last uid */
686 g_node_traverse(folder->node, G_IN_ORDER, G_TRAVERSE_ALL, -1, imap_reset_uid_lists_func, NULL);
689 /* Send CAPABILITY, and examine the server's response to see whether this
690 * connection is pre-authenticated or not and build a list of CAPABILITIES. */
691 static gint imap_greeting(IMAPSession *session)
696 imap_gen_send(session, "CAPABILITY");
698 argbuf = g_ptr_array_new();
700 if (imap_cmd_ok(session, argbuf) != IMAP_SUCCESS ||
701 ((capstr = search_array_str(argbuf, "CAPABILITY ")) == NULL)) {
702 ptr_array_free_strings(argbuf);
703 g_ptr_array_free(argbuf, TRUE);
707 session->authenticated = search_array_str(argbuf, "PREAUTH") != NULL;
709 capstr += strlen("CAPABILITY ");
711 IMAP_SESSION(session)->capability = g_strsplit(capstr, " ", 0);
713 ptr_array_free_strings(argbuf);
714 g_ptr_array_free(argbuf, TRUE);
716 if (imap_has_capability(session, "UIDPLUS"))
717 session->uidplus = TRUE;
722 static gint imap_auth(IMAPSession *session, const gchar *user, const gchar *pass,
727 if (type == 0 || type == IMAP_AUTH_LOGIN)
728 ok = imap_cmd_login(session, user, pass);
730 ok = imap_cmd_authenticate(session, user, pass, type);
732 if (ok == IMAP_SUCCESS)
733 session->authenticated = TRUE;
738 static IMAPSession *imap_session_get(Folder *folder)
740 RemoteFolder *rfolder = REMOTE_FOLDER(folder);
741 IMAPSession *session = NULL;
743 g_return_val_if_fail(folder != NULL, NULL);
744 g_return_val_if_fail(FOLDER_CLASS(folder) == &imap_class, NULL);
745 g_return_val_if_fail(folder->account != NULL, NULL);
747 if (prefs_common.work_offline && !imap_gtk_should_override()) {
751 /* Make sure we have a session */
752 if (rfolder->session != NULL) {
753 session = IMAP_SESSION(rfolder->session);
755 imap_reset_uid_lists(folder);
756 session = imap_session_new(folder->account);
761 if (SESSION(session)->sock->state == CONN_DISCONNECTED) {
762 debug_print("IMAP server disconnected\n");
763 session_destroy(SESSION(session));
764 imap_reset_uid_lists(folder);
765 session = imap_session_new(folder->account);
768 /* Make sure session is authenticated */
769 if (!IMAP_SESSION(session)->authenticated)
770 imap_session_authenticate(IMAP_SESSION(session), folder->account);
771 if (!IMAP_SESSION(session)->authenticated) {
772 session_destroy(SESSION(session));
773 rfolder->session = NULL;
777 /* Make sure we have parsed the IMAP namespace */
778 imap_parse_namespace(IMAP_SESSION(session),
779 IMAP_FOLDER(folder));
781 /* I think the point of this code is to avoid sending a
782 * keepalive if we've used the session recently and therefore
783 * think it's still alive. Unfortunately, most of the code
784 * does not yet check for errors on the socket, and so if the
785 * connection drops we don't notice until the timeout expires.
786 * A better solution than sending a NOOP every time would be
787 * for every command to be prepared to retry until it is
788 * successfully sent. -- mbp */
789 if (time(NULL) - SESSION(session)->last_access_time > SESSION_TIMEOUT_INTERVAL) {
790 /* verify that the session is still alive */
791 if (imap_cmd_noop(session) != IMAP_SUCCESS) {
792 /* Check if this is the first try to establish a
793 connection, if yes we don't try to reconnect */
794 if (rfolder->session == NULL) {
795 log_warning(_("Connecting to %s failed"),
796 folder->account->recv_server);
797 session_destroy(SESSION(session));
800 log_warning(_("IMAP4 connection to %s has been"
801 " disconnected. Reconnecting...\n"),
802 folder->account->recv_server);
803 statusbar_print_all(_("IMAP4 connection to %s has been"
804 " disconnected. Reconnecting...\n"),
805 folder->account->recv_server);
806 session_destroy(SESSION(session));
807 /* Clear folders session to make imap_session_get create
808 a new session, because of rfolder->session == NULL
809 it will not try to reconnect again and so avoid an
811 rfolder->session = NULL;
812 session = imap_session_get(folder);
818 rfolder->session = SESSION(session);
820 return IMAP_SESSION(session);
823 static IMAPSession *imap_session_new(const PrefsAccount *account)
825 IMAPSession *session;
830 /* FIXME: IMAP over SSL only... */
833 port = account->set_imapport ? account->imapport
834 : account->ssl_imap == SSL_TUNNEL ? IMAPS_PORT : IMAP4_PORT;
835 ssl_type = account->ssl_imap;
837 port = account->set_imapport ? account->imapport
841 if (account->set_tunnelcmd) {
842 log_message(_("creating tunneled IMAP4 connection\n"));
844 if ((imap_sock = imap_open_tunnel(account->recv_server,
848 if ((imap_sock = imap_open_tunnel(account->recv_server,
849 account->tunnelcmd)) == NULL)
853 g_return_val_if_fail(account->recv_server != NULL, NULL);
855 log_message(_("creating IMAP4 connection to %s:%d ...\n"),
856 account->recv_server, port);
859 if ((imap_sock = imap_open(account->recv_server, port,
862 if ((imap_sock = imap_open(account->recv_server, port)) == NULL)
867 session = g_new0(IMAPSession, 1);
868 session_init(SESSION(session));
869 SESSION(session)->type = SESSION_IMAP;
870 SESSION(session)->server = g_strdup(account->recv_server);
871 SESSION(session)->sock = imap_sock;
873 SESSION(session)->destroy = imap_session_destroy;
875 session->capability = NULL;
877 session->authenticated = FALSE;
878 session->mbox = NULL;
879 session->cmd_count = 0;
881 /* Only need to log in if the connection was not PREAUTH */
882 if (imap_greeting(session) != IMAP_SUCCESS) {
883 session_destroy(SESSION(session));
888 if (account->ssl_imap == SSL_STARTTLS &&
889 imap_has_capability(session, "STARTTLS")) {
892 ok = imap_cmd_starttls(session);
893 if (ok != IMAP_SUCCESS) {
894 log_warning(_("Can't start TLS session.\n"));
895 session_destroy(SESSION(session));
898 if (!ssl_init_socket_with_method(SESSION(session)->sock,
900 session_destroy(SESSION(session));
904 imap_free_capabilities(session);
905 session->authenticated = FALSE;
906 session->uidplus = FALSE;
907 session->cmd_count = 1;
909 if (imap_greeting(session) != IMAP_SUCCESS) {
910 session_destroy(SESSION(session));
915 log_message("IMAP connection is %s-authenticated\n",
916 (session->authenticated) ? "pre" : "un");
921 static void imap_session_authenticate(IMAPSession *session,
922 const PrefsAccount *account)
926 g_return_if_fail(account->userid != NULL);
928 pass = account->passwd;
931 tmp_pass = input_dialog_query_password(account->recv_server, account->userid);
934 Xstrdup_a(pass, tmp_pass, {g_free(tmp_pass); return;});
937 statusbar_print_all(_("Connecting to IMAP4 server %s...\n"),
938 account->recv_server);
939 if (imap_auth(session, account->userid, pass, account->imap_auth_type) != IMAP_SUCCESS) {
940 imap_cmd_logout(session);
946 session->authenticated = TRUE;
949 static void imap_session_destroy(Session *session)
951 imap_free_capabilities(IMAP_SESSION(session));
952 g_free(IMAP_SESSION(session)->mbox);
953 sock_close(session->sock);
954 session->sock = NULL;
957 static gchar *imap_fetch_msg(Folder *folder, FolderItem *item, gint uid)
959 return imap_fetch_msg_full(folder, item, uid, TRUE, TRUE);
962 static guint get_size_with_lfs(MsgInfo *info)
971 fp = procmsg_open_message(info);
975 while (fgets(buf, sizeof (buf), fp) != NULL) {
977 if (!strstr(buf, "\r") && strstr(buf, "\n"))
985 static gchar *imap_fetch_msg_full(Folder *folder, FolderItem *item, gint uid,
986 gboolean headers, gboolean body)
988 gchar *path, *filename;
989 IMAPSession *session;
992 g_return_val_if_fail(folder != NULL, NULL);
993 g_return_val_if_fail(item != NULL, NULL);
998 path = folder_item_get_path(item);
999 if (!is_dir_exist(path))
1000 make_dir_hier(path);
1001 filename = g_strconcat(path, G_DIR_SEPARATOR_S, itos(uid), NULL);
1004 if (is_file_exist(filename)) {
1005 /* see whether the local file represents the whole message
1006 * or not. As the IMAP server reports size with \r chars,
1007 * we have to update the local file (UNIX \n only) size */
1008 MsgInfo *msginfo = imap_parse_msg(filename, item);
1009 MsgInfo *cached = msgcache_get_msg(item->cache,uid);
1010 guint have_size = get_size_with_lfs(msginfo);
1011 debug_print("message %d has been already %scached (%d/%d).\n", uid,
1012 have_size == cached->size ? "fully ":"",
1013 have_size, cached? (int)cached->size : -1);
1015 if (cached && (cached->size == have_size || !body)) {
1016 procmsg_msginfo_free(cached);
1017 procmsg_msginfo_free(msginfo);
1020 procmsg_msginfo_free(cached);
1021 procmsg_msginfo_free(msginfo);
1025 session = imap_session_get(folder);
1031 debug_print("IMAP fetching messages\n");
1032 ok = imap_select(session, IMAP_FOLDER(folder), item->path,
1033 NULL, NULL, NULL, NULL, FALSE);
1034 if (ok != IMAP_SUCCESS) {
1035 g_warning("can't select mailbox %s\n", item->path);
1040 debug_print("getting message %d...\n", uid);
1041 ok = imap_cmd_fetch(session, (guint32)uid, filename, headers, body);
1043 if (ok != IMAP_SUCCESS) {
1044 g_warning("can't fetch message %d\n", uid);
1052 static gint imap_add_msg(Folder *folder, FolderItem *dest,
1053 const gchar *file, MsgFlags *flags)
1057 MsgFileInfo fileinfo;
1059 g_return_val_if_fail(file != NULL, -1);
1061 fileinfo.msginfo = NULL;
1062 fileinfo.file = (gchar *)file;
1063 fileinfo.flags = flags;
1064 file_list.data = &fileinfo;
1065 file_list.next = NULL;
1067 ret = imap_add_msgs(folder, dest, &file_list, NULL);
1071 static gint imap_add_msgs(Folder *folder, FolderItem *dest, GSList *file_list,
1072 GRelation *relation)
1075 IMAPSession *session;
1076 guint32 last_uid = 0;
1078 MsgFileInfo *fileinfo;
1081 g_return_val_if_fail(folder != NULL, -1);
1082 g_return_val_if_fail(dest != NULL, -1);
1083 g_return_val_if_fail(file_list != NULL, -1);
1085 session = imap_session_get(folder);
1086 if (!session) return -1;
1088 destdir = imap_get_real_path(IMAP_FOLDER(folder), dest->path);
1090 for (cur = file_list; cur != NULL; cur = cur->next) {
1091 IMAPFlags iflags = 0;
1092 guint32 new_uid = 0;
1094 fileinfo = (MsgFileInfo *)cur->data;
1096 if (fileinfo->flags) {
1097 if (MSG_IS_MARKED(*fileinfo->flags))
1098 iflags |= IMAP_FLAG_FLAGGED;
1099 if (MSG_IS_REPLIED(*fileinfo->flags))
1100 iflags |= IMAP_FLAG_ANSWERED;
1101 if (!MSG_IS_UNREAD(*fileinfo->flags))
1102 iflags |= IMAP_FLAG_SEEN;
1105 if (dest->stype == F_OUTBOX ||
1106 dest->stype == F_QUEUE ||
1107 dest->stype == F_DRAFT ||
1108 dest->stype == F_TRASH)
1109 iflags |= IMAP_FLAG_SEEN;
1111 ok = imap_cmd_append(session, destdir, fileinfo->file, iflags,
1114 if (ok != IMAP_SUCCESS) {
1115 g_warning("can't append message %s\n", fileinfo->file);
1120 if (relation != NULL)
1121 g_relation_insert(relation, fileinfo->msginfo != NULL ?
1122 (gpointer) fileinfo->msginfo : (gpointer) fileinfo,
1123 GINT_TO_POINTER(dest->last_num + 1));
1124 if (last_uid < new_uid)
1133 static gint imap_do_copy_msgs(Folder *folder, FolderItem *dest,
1134 MsgInfoList *msglist, GRelation *relation)
1138 GSList *seq_list, *cur;
1140 IMAPSession *session;
1141 gint ok = IMAP_SUCCESS;
1142 GRelation *uid_mapping;
1145 g_return_val_if_fail(folder != NULL, -1);
1146 g_return_val_if_fail(dest != NULL, -1);
1147 g_return_val_if_fail(msglist != NULL, -1);
1149 session = imap_session_get(folder);
1153 msginfo = (MsgInfo *)msglist->data;
1155 src = msginfo->folder;
1157 g_warning("the src folder is identical to the dest.\n");
1161 ok = imap_select(session, IMAP_FOLDER(folder), msginfo->folder->path,
1162 NULL, NULL, NULL, NULL, FALSE);
1163 if (ok != IMAP_SUCCESS) {
1167 destdir = imap_get_real_path(IMAP_FOLDER(folder), dest->path);
1168 seq_list = imap_get_seq_set_from_msglist(msglist);
1169 uid_mapping = g_relation_new(2);
1170 g_relation_index(uid_mapping, 0, g_direct_hash, g_direct_equal);
1172 for (cur = seq_list; cur != NULL; cur = g_slist_next(cur)) {
1173 gchar *seq_set = (gchar *)cur->data;
1175 debug_print("Copying message %s%c[%s] to %s ...\n",
1176 src->path, G_DIR_SEPARATOR,
1179 ok = imap_cmd_copy(session, seq_set, destdir, uid_mapping);
1180 if (ok != IMAP_SUCCESS) {
1181 g_relation_destroy(uid_mapping);
1182 imap_seq_set_free(seq_list);
1187 for (cur = msglist; cur != NULL; cur = g_slist_next(cur)) {
1188 MsgInfo *msginfo = (MsgInfo *)cur->data;
1191 tuples = g_relation_select(uid_mapping,
1192 GINT_TO_POINTER(msginfo->msgnum),
1194 if (tuples->len > 0) {
1195 gint num = GPOINTER_TO_INT(g_tuples_index(tuples, 0, 1));
1196 g_relation_insert(relation, msginfo,
1197 GPOINTER_TO_INT(num));
1201 g_relation_insert(relation, msginfo,
1202 GPOINTER_TO_INT(0));
1203 g_tuples_destroy(tuples);
1206 g_relation_destroy(uid_mapping);
1207 imap_seq_set_free(seq_list);
1211 IMAP_FOLDER_ITEM(dest)->lastuid = 0;
1212 IMAP_FOLDER_ITEM(dest)->uid_next = 0;
1213 g_slist_free(IMAP_FOLDER_ITEM(dest)->uid_list);
1214 IMAP_FOLDER_ITEM(dest)->uid_list = NULL;
1216 if (ok == IMAP_SUCCESS)
1222 static gint imap_copy_msg(Folder *folder, FolderItem *dest, MsgInfo *msginfo)
1226 g_return_val_if_fail(msginfo != NULL, -1);
1228 msglist.data = msginfo;
1229 msglist.next = NULL;
1231 return imap_copy_msgs(folder, dest, &msglist, NULL);
1234 static gint imap_copy_msgs(Folder *folder, FolderItem *dest,
1235 MsgInfoList *msglist, GRelation *relation)
1241 g_return_val_if_fail(folder != NULL, -1);
1242 g_return_val_if_fail(dest != NULL, -1);
1243 g_return_val_if_fail(msglist != NULL, -1);
1245 msginfo = (MsgInfo *)msglist->data;
1246 g_return_val_if_fail(msginfo->folder != NULL, -1);
1248 if (folder == msginfo->folder->folder)
1249 return imap_do_copy_msgs(folder, dest, msglist, relation);
1251 file_list = procmsg_get_message_file_list(msglist);
1252 g_return_val_if_fail(file_list != NULL, -1);
1254 ret = imap_add_msgs(folder, dest, file_list, relation);
1256 procmsg_message_file_list_free(file_list);
1262 static gint imap_do_remove_msgs(Folder *folder, FolderItem *dest,
1263 MsgInfoList *msglist, GRelation *relation)
1266 GSList *seq_list = NULL, *cur;
1268 IMAPSession *session;
1269 gint ok = IMAP_SUCCESS;
1270 GRelation *uid_mapping;
1272 g_return_val_if_fail(folder != NULL, -1);
1273 g_return_val_if_fail(dest != NULL, -1);
1274 g_return_val_if_fail(msglist != NULL, -1);
1276 session = imap_session_get(folder);
1280 msginfo = (MsgInfo *)msglist->data;
1282 ok = imap_select(session, IMAP_FOLDER(folder), msginfo->folder->path,
1283 NULL, NULL, NULL, NULL, FALSE);
1284 if (ok != IMAP_SUCCESS) {
1288 destdir = imap_get_real_path(IMAP_FOLDER(folder), dest->path);
1289 for (cur = msglist; cur; cur = cur->next) {
1290 msginfo = (MsgInfo *)cur->data;
1291 seq_list = g_slist_append(seq_list, GINT_TO_POINTER(msginfo->msgnum));
1294 uid_mapping = g_relation_new(2);
1295 g_relation_index(uid_mapping, 0, g_direct_hash, g_direct_equal);
1297 ok = imap_set_message_flags
1298 (IMAP_SESSION(REMOTE_FOLDER(folder)->session),
1299 seq_list, IMAP_FLAG_DELETED, TRUE);
1300 if (ok != IMAP_SUCCESS) {
1301 log_warning(_("can't set deleted flags\n"));
1304 ok = imap_cmd_expunge(session, NULL);
1305 if (ok != IMAP_SUCCESS) {
1306 log_warning(_("can't expunge\n"));
1310 g_relation_destroy(uid_mapping);
1311 g_slist_free(seq_list);
1315 if (ok == IMAP_SUCCESS)
1321 static gint imap_remove_msgs(Folder *folder, FolderItem *dest,
1322 MsgInfoList *msglist, GRelation *relation)
1326 g_return_val_if_fail(folder != NULL, -1);
1327 g_return_val_if_fail(dest != NULL, -1);
1328 g_return_val_if_fail(msglist != NULL, -1);
1330 msginfo = (MsgInfo *)msglist->data;
1331 g_return_val_if_fail(msginfo->folder != NULL, -1);
1333 return imap_do_remove_msgs(folder, dest, msglist, relation);
1336 static gint imap_remove_all_msg(Folder *folder, FolderItem *item)
1338 GSList *list = folder_item_get_msg_list(item);
1339 gint res = imap_remove_msgs(folder, item, list, NULL);
1344 static gboolean imap_is_msg_changed(Folder *folder, FolderItem *item,
1347 /* TODO: properly implement this method */
1351 static gint imap_close(Folder *folder, FolderItem *item)
1354 IMAPSession *session;
1356 g_return_val_if_fail(folder != NULL, -1);
1357 g_return_val_if_fail(item != NULL, -1);
1358 g_return_val_if_fail(item->path != NULL, -1);
1360 session = imap_session_get(folder);
1361 if (!session) return -1;
1363 if (session->mbox) {
1364 if (strcmp2(session->mbox, item->path) != 0) return -1;
1366 ok = imap_cmd_close(session);
1367 if (ok != IMAP_SUCCESS)
1368 log_warning(_("can't close folder\n"));
1370 g_free(session->mbox);
1372 session->mbox = NULL;
1380 static gint imap_scan_tree(Folder *folder)
1382 FolderItem *item = NULL;
1383 IMAPSession *session;
1384 gchar *root_folder = NULL;
1386 g_return_val_if_fail(folder != NULL, -1);
1387 g_return_val_if_fail(folder->account != NULL, -1);
1389 session = imap_session_get(folder);
1391 if (!folder->node) {
1392 folder_tree_destroy(folder);
1393 item = folder_item_new(folder, folder->name, NULL);
1394 item->folder = folder;
1395 folder->node = item->node = g_node_new(item);
1400 if (folder->account->imap_dir && *folder->account->imap_dir) {
1405 Xstrdup_a(root_folder, folder->account->imap_dir, return -1);
1406 extract_quote(root_folder, '"');
1407 subst_char(root_folder,
1408 imap_get_path_separator(IMAP_FOLDER(folder),
1411 strtailchomp(root_folder, '/');
1412 real_path = imap_get_real_path
1413 (IMAP_FOLDER(folder), root_folder);
1414 debug_print("IMAP root directory: %s\n", real_path);
1416 /* check if root directory exist */
1417 argbuf = g_ptr_array_new();
1418 ok = imap_cmd_list(session, NULL, real_path, argbuf);
1419 if (ok != IMAP_SUCCESS ||
1420 search_array_str(argbuf, "LIST ") == NULL) {
1421 log_warning(_("root folder %s does not exist\n"), real_path);
1422 g_ptr_array_free(argbuf, TRUE);
1425 if (!folder->node) {
1426 item = folder_item_new(folder, folder->name, NULL);
1427 item->folder = folder;
1428 folder->node = item->node = g_node_new(item);
1432 g_ptr_array_free(argbuf, TRUE);
1437 item = FOLDER_ITEM(folder->node->data);
1438 if (!item || ((item->path || root_folder) &&
1439 strcmp2(item->path, root_folder) != 0)) {
1440 folder_tree_destroy(folder);
1441 item = folder_item_new(folder, folder->name, root_folder);
1442 item->folder = folder;
1443 folder->node = item->node = g_node_new(item);
1446 imap_scan_tree_recursive(session, FOLDER_ITEM(folder->node->data));
1447 imap_create_missing_folders(folder);
1452 static gint imap_scan_tree_recursive(IMAPSession *session, FolderItem *item)
1455 IMAPFolder *imapfolder;
1456 FolderItem *new_item;
1457 GSList *item_list, *cur;
1460 gchar *wildcard_path;
1464 g_return_val_if_fail(item != NULL, -1);
1465 g_return_val_if_fail(item->folder != NULL, -1);
1466 g_return_val_if_fail(item->no_sub == FALSE, -1);
1468 folder = item->folder;
1469 imapfolder = IMAP_FOLDER(folder);
1471 separator = imap_get_path_separator(imapfolder, item->path);
1473 if (folder->ui_func)
1474 folder->ui_func(folder, item, folder->ui_func_data);
1477 wildcard[0] = separator;
1480 real_path = imap_get_real_path(imapfolder, item->path);
1484 real_path = g_strdup("");
1487 Xstrcat_a(wildcard_path, real_path, wildcard,
1488 {g_free(real_path); return IMAP_ERROR;});
1489 QUOTE_IF_REQUIRED(wildcard_path, wildcard_path);
1491 imap_gen_send(session, "LIST \"\" %s",
1494 strtailchomp(real_path, separator);
1495 item_list = imap_parse_list(imapfolder, session, real_path, NULL);
1498 node = item->node->children;
1499 while (node != NULL) {
1500 FolderItem *old_item = FOLDER_ITEM(node->data);
1501 GNode *next = node->next;
1504 for (cur = item_list; cur != NULL; cur = cur->next) {
1505 FolderItem *cur_item = FOLDER_ITEM(cur->data);
1506 if (!strcmp2(old_item->path, cur_item->path)) {
1507 new_item = cur_item;
1512 debug_print("folder '%s' not found. removing...\n",
1514 folder_item_remove(old_item);
1516 old_item->no_sub = new_item->no_sub;
1517 old_item->no_select = new_item->no_select;
1518 if (old_item->no_sub == TRUE && node->children) {
1519 debug_print("folder '%s' doesn't have "
1520 "subfolders. removing...\n",
1522 folder_item_remove_children(old_item);
1529 for (cur = item_list; cur != NULL; cur = cur->next) {
1530 FolderItem *cur_item = FOLDER_ITEM(cur->data);
1532 for (node = item->node->children; node != NULL;
1533 node = node->next) {
1534 if (!strcmp2(FOLDER_ITEM(node->data)->path,
1536 new_item = FOLDER_ITEM(node->data);
1537 folder_item_destroy(cur_item);
1543 new_item = cur_item;
1544 debug_print("new folder '%s' found.\n", new_item->path);
1545 folder_item_append(item, new_item);
1548 if (!strcmp(new_item->path, "INBOX")) {
1549 new_item->stype = F_INBOX;
1550 folder->inbox = new_item;
1551 } else if (!folder_item_parent(item) || item->stype == F_INBOX) {
1554 base = g_path_get_basename(new_item->path);
1556 if (!folder->outbox && !g_ascii_strcasecmp(base, "Sent")) {
1557 new_item->stype = F_OUTBOX;
1558 folder->outbox = new_item;
1559 } else if (!folder->draft && !g_ascii_strcasecmp(base, "Drafts")) {
1560 new_item->stype = F_DRAFT;
1561 folder->draft = new_item;
1562 } else if (!folder->queue && !g_ascii_strcasecmp(base, "Queue")) {
1563 new_item->stype = F_QUEUE;
1564 folder->queue = new_item;
1565 } else if (!folder->trash && !g_ascii_strcasecmp(base, "Trash")) {
1566 new_item->stype = F_TRASH;
1567 folder->trash = new_item;
1572 if (new_item->no_sub == FALSE)
1573 imap_scan_tree_recursive(session, new_item);
1576 g_slist_free(item_list);
1578 return IMAP_SUCCESS;
1581 static GSList *imap_parse_list(IMAPFolder *folder, IMAPSession *session,
1582 const gchar *real_path, gchar *separator)
1584 gchar buf[IMAPBUFSIZE];
1586 gchar separator_str[16];
1589 gchar *loc_name, *loc_path;
1590 GSList *item_list = NULL;
1592 FolderItem *new_item;
1594 debug_print("getting list of %s ...\n",
1595 *real_path ? real_path : "\"\"");
1597 str = g_string_new(NULL);
1600 if (sock_gets(SESSION(session)->sock, buf, sizeof(buf)) <= 0) {
1601 log_warning(_("error occurred while getting LIST.\n"));
1605 if (buf[0] != '*' || buf[1] != ' ') {
1606 log_print("IMAP4< %s\n", buf);
1607 if (sscanf(buf, "%*d %16s", buf) < 1 ||
1608 strcmp(buf, "OK") != 0)
1609 log_warning(_("error occurred while getting LIST.\n"));
1613 debug_print("IMAP4< %s\n", buf);
1615 g_string_assign(str, buf);
1617 if (strncmp(p, "LIST ", 5) != 0) continue;
1620 if (*p != '(') continue;
1622 p = strchr_cpy(p, ')', flags, sizeof(flags));
1624 while (*p == ' ') p++;
1626 p = strchr_cpy(p, ' ', separator_str, sizeof(separator_str));
1628 extract_quote(separator_str, '"');
1629 if (!strcmp(separator_str, "NIL"))
1630 separator_str[0] = '\0';
1632 *separator = separator_str[0];
1635 while (*p == ' ') p++;
1636 if (*p == '{' || *p == '"')
1637 p = imap_parse_atom(SESSION(session)->sock, p,
1638 buf, sizeof(buf), str);
1640 strncpy2(buf, p, sizeof(buf));
1641 strtailchomp(buf, separator_str[0]);
1642 if (buf[0] == '\0') continue;
1643 if (!strcmp(buf, real_path)) continue;
1645 if (separator_str[0] != '\0')
1646 subst_char(buf, separator_str[0], '/');
1647 base = g_path_get_basename(buf);
1648 if (base[0] == '.') continue;
1650 loc_name = imap_modified_utf7_to_utf8(base);
1651 loc_path = imap_modified_utf7_to_utf8(buf);
1652 new_item = folder_item_new(FOLDER(folder), loc_name, loc_path);
1653 if (strcasestr(flags, "\\Noinferiors") != NULL)
1654 new_item->no_sub = TRUE;
1655 if (strcmp(buf, "INBOX") != 0 &&
1656 strcasestr(flags, "\\Noselect") != NULL)
1657 new_item->no_select = TRUE;
1659 item_list = g_slist_append(item_list, new_item);
1661 debug_print("folder '%s' found.\n", loc_path);
1667 g_string_free(str, TRUE);
1672 static gint imap_create_tree(Folder *folder)
1674 g_return_val_if_fail(folder != NULL, -1);
1675 g_return_val_if_fail(folder->node != NULL, -1);
1676 g_return_val_if_fail(folder->node->data != NULL, -1);
1677 g_return_val_if_fail(folder->account != NULL, -1);
1679 imap_scan_tree(folder);
1680 imap_create_missing_folders(folder);
1685 static void imap_create_missing_folders(Folder *folder)
1687 g_return_if_fail(folder != NULL);
1690 folder->inbox = imap_create_special_folder
1691 (folder, F_INBOX, "INBOX");
1693 if (!folder->outbox)
1694 folder->outbox = imap_create_special_folder
1695 (folder, F_OUTBOX, "Sent");
1697 folder->draft = imap_create_special_folder
1698 (folder, F_DRAFT, "Drafts");
1700 folder->queue = imap_create_special_folder
1701 (folder, F_QUEUE, "Queue");
1704 folder->trash = imap_create_special_folder
1705 (folder, F_TRASH, "Trash");
1708 static FolderItem *imap_create_special_folder(Folder *folder,
1709 SpecialFolderItemType stype,
1713 FolderItem *new_item;
1715 g_return_val_if_fail(folder != NULL, NULL);
1716 g_return_val_if_fail(folder->node != NULL, NULL);
1717 g_return_val_if_fail(folder->node->data != NULL, NULL);
1718 g_return_val_if_fail(folder->account != NULL, NULL);
1719 g_return_val_if_fail(name != NULL, NULL);
1721 item = FOLDER_ITEM(folder->node->data);
1722 new_item = imap_create_folder(folder, item, name);
1725 g_warning("Can't create '%s'\n", name);
1726 if (!folder->inbox) return NULL;
1728 new_item = imap_create_folder(folder, folder->inbox, name);
1730 g_warning("Can't create '%s' under INBOX\n", name);
1732 new_item->stype = stype;
1734 new_item->stype = stype;
1739 static gchar *imap_folder_get_path(Folder *folder)
1743 g_return_val_if_fail(folder != NULL, NULL);
1744 g_return_val_if_fail(folder->account != NULL, NULL);
1746 folder_path = g_strconcat(get_imap_cache_dir(),
1748 folder->account->recv_server,
1750 folder->account->userid,
1756 static gchar *imap_item_get_path(Folder *folder, FolderItem *item)
1758 gchar *folder_path, *path;
1760 g_return_val_if_fail(folder != NULL, NULL);
1761 g_return_val_if_fail(item != NULL, NULL);
1762 folder_path = imap_folder_get_path(folder);
1764 g_return_val_if_fail(folder_path != NULL, NULL);
1765 if (folder_path[0] == G_DIR_SEPARATOR) {
1767 path = g_strconcat(folder_path, G_DIR_SEPARATOR_S,
1770 path = g_strdup(folder_path);
1773 path = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S,
1774 folder_path, G_DIR_SEPARATOR_S,
1777 path = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S,
1780 g_free(folder_path);
1785 static FolderItem *imap_create_folder(Folder *folder, FolderItem *parent,
1788 gchar *dirpath, *imap_path;
1789 IMAPSession *session;
1790 FolderItem *new_item;
1796 g_return_val_if_fail(folder != NULL, NULL);
1797 g_return_val_if_fail(folder->account != NULL, NULL);
1798 g_return_val_if_fail(parent != NULL, NULL);
1799 g_return_val_if_fail(name != NULL, NULL);
1801 session = imap_session_get(folder);
1802 if (!session) return NULL;
1804 if (!folder_item_parent(parent) && strcmp(name, "INBOX") == 0)
1805 dirpath = g_strdup(name);
1806 else if (parent->path)
1807 dirpath = g_strconcat(parent->path, "/", name, NULL);
1808 else if ((p = strchr(name, '/')) != NULL && *(p + 1) != '\0')
1809 dirpath = g_strdup(name);
1810 else if (folder->account->imap_dir && *folder->account->imap_dir) {
1813 Xstrdup_a(imap_dir, folder->account->imap_dir, return NULL);
1814 strtailchomp(imap_dir, '/');
1815 dirpath = g_strconcat(imap_dir, "/", name, NULL);
1817 dirpath = g_strdup(name);
1819 /* keep trailing directory separator to create a folder that contains
1821 imap_path = imap_utf8_to_modified_utf7(dirpath);
1822 strtailchomp(dirpath, '/');
1823 Xstrdup_a(new_name, name, {g_free(dirpath); return NULL;});
1824 strtailchomp(new_name, '/');
1825 separator = imap_get_path_separator(IMAP_FOLDER(folder), imap_path);
1826 imap_path_separator_subst(imap_path, separator);
1827 subst_char(new_name, '/', separator);
1829 if (strcmp(name, "INBOX") != 0) {
1832 gboolean exist = FALSE;
1834 argbuf = g_ptr_array_new();
1835 ok = imap_cmd_list(session, NULL, imap_path,
1837 if (ok != IMAP_SUCCESS) {
1838 log_warning(_("can't create mailbox: LIST failed\n"));
1841 ptr_array_free_strings(argbuf);
1842 g_ptr_array_free(argbuf, TRUE);
1846 for (i = 0; i < argbuf->len; i++) {
1848 str = g_ptr_array_index(argbuf, i);
1849 if (!strncmp(str, "LIST ", 5)) {
1854 ptr_array_free_strings(argbuf);
1855 g_ptr_array_free(argbuf, TRUE);
1858 ok = imap_cmd_create(session, imap_path);
1859 if (ok != IMAP_SUCCESS) {
1860 log_warning(_("can't create mailbox\n"));
1868 new_item = folder_item_new(folder, new_name, dirpath);
1869 folder_item_append(parent, new_item);
1873 dirpath = folder_item_get_path(new_item);
1874 if (!is_dir_exist(dirpath))
1875 make_dir_hier(dirpath);
1881 static gint imap_rename_folder(Folder *folder, FolderItem *item,
1886 gchar *real_oldpath;
1887 gchar *real_newpath;
1889 gchar *old_cache_dir;
1890 gchar *new_cache_dir;
1891 IMAPSession *session;
1894 gint exists, recent, unseen;
1895 guint32 uid_validity;
1897 g_return_val_if_fail(folder != NULL, -1);
1898 g_return_val_if_fail(item != NULL, -1);
1899 g_return_val_if_fail(item->path != NULL, -1);
1900 g_return_val_if_fail(name != NULL, -1);
1902 if (strchr(name, imap_get_path_separator(IMAP_FOLDER(folder), item->path)) != NULL) {
1903 g_warning(_("New folder name must not contain the namespace "
1908 session = imap_session_get(folder);
1909 if (!session) return -1;
1911 real_oldpath = imap_get_real_path(IMAP_FOLDER(folder), item->path);
1913 g_free(session->mbox);
1914 session->mbox = NULL;
1915 ok = imap_cmd_examine(session, "INBOX",
1916 &exists, &recent, &unseen, &uid_validity, FALSE);
1917 if (ok != IMAP_SUCCESS) {
1918 g_free(real_oldpath);
1922 separator = imap_get_path_separator(IMAP_FOLDER(folder), item->path);
1923 if (strchr(item->path, G_DIR_SEPARATOR)) {
1924 dirpath = g_path_get_dirname(item->path);
1925 newpath = g_strconcat(dirpath, G_DIR_SEPARATOR_S, name, NULL);
1928 newpath = g_strdup(name);
1930 real_newpath = imap_utf8_to_modified_utf7(newpath);
1931 imap_path_separator_subst(real_newpath, separator);
1933 ok = imap_cmd_rename(session, real_oldpath, real_newpath);
1934 if (ok != IMAP_SUCCESS) {
1935 log_warning(_("can't rename mailbox: %s to %s\n"),
1936 real_oldpath, real_newpath);
1937 g_free(real_oldpath);
1939 g_free(real_newpath);
1944 item->name = g_strdup(name);
1946 old_cache_dir = folder_item_get_path(item);
1948 paths[0] = g_strdup(item->path);
1950 g_node_traverse(item->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
1951 imap_rename_folder_func, paths);
1953 if (is_dir_exist(old_cache_dir)) {
1954 new_cache_dir = folder_item_get_path(item);
1955 if (rename(old_cache_dir, new_cache_dir) < 0) {
1956 FILE_OP_ERROR(old_cache_dir, "rename");
1958 g_free(new_cache_dir);
1961 g_free(old_cache_dir);
1964 g_free(real_oldpath);
1965 g_free(real_newpath);
1970 static gint imap_remove_folder(Folder *folder, FolderItem *item)
1973 IMAPSession *session;
1976 gint exists, recent, unseen;
1977 guint32 uid_validity;
1979 g_return_val_if_fail(folder != NULL, -1);
1980 g_return_val_if_fail(item != NULL, -1);
1981 g_return_val_if_fail(item->path != NULL, -1);
1983 session = imap_session_get(folder);
1984 if (!session) return -1;
1986 path = imap_get_real_path(IMAP_FOLDER(folder), item->path);
1988 ok = imap_cmd_examine(session, "INBOX",
1989 &exists, &recent, &unseen, &uid_validity, FALSE);
1990 if (ok != IMAP_SUCCESS) {
1995 ok = imap_cmd_delete(session, path);
1996 if (ok != IMAP_SUCCESS) {
1997 log_warning(_("can't delete mailbox\n"));
2003 cache_dir = folder_item_get_path(item);
2004 if (is_dir_exist(cache_dir) && remove_dir_recursive(cache_dir) < 0)
2005 g_warning("can't remove directory '%s'\n", cache_dir);
2007 folder_item_remove(item);
2012 typedef struct _uncached_data {
2013 IMAPSession *session;
2015 MsgNumberList *numlist;
2021 static void *imap_get_uncached_messages_thread(void *data)
2023 uncached_data *stuff = (uncached_data *)data;
2024 IMAPSession *session = stuff->session;
2025 FolderItem *item = stuff->item;
2026 MsgNumberList *numlist = stuff->numlist;
2029 GSList *newlist = NULL;
2030 GSList *llast = NULL;
2031 GString *str = NULL;
2033 GSList *seq_list, *cur;
2036 stuff->total = g_slist_length(numlist);
2039 if (session == NULL || item == NULL || item->folder == NULL
2040 || FOLDER_CLASS(item->folder) != &imap_class) {
2045 seq_list = imap_get_seq_set_from_numlist(numlist);
2046 for (cur = seq_list; cur != NULL; cur = g_slist_next(cur)) {
2047 imapset = cur->data;
2049 if (!imapset || strlen(imapset) == 0)
2052 if (imap_cmd_envelope(session, imapset)
2054 log_warning(_("can't get envelope\n"));
2058 str = g_string_new(NULL);
2061 if ((tmp =sock_getline(SESSION(session)->sock)) == NULL) {
2062 log_warning(_("error occurred while getting envelope.\n"));
2063 g_string_free(str, TRUE);
2068 if (tmp[0] != '*' || tmp[1] != ' ') {
2069 log_print("IMAP4< %s\n", tmp);
2073 if (strstr(tmp, "FETCH") == NULL) {
2074 log_print("IMAP4< %s\n", tmp);
2078 log_print("IMAP4< %s\n", tmp);
2079 g_string_assign(str, tmp);
2084 msginfo = imap_parse_envelope
2085 (SESSION(session)->sock, item, str);
2087 log_warning(_("can't parse envelope: %s\n"), str->str);
2090 if (item->stype == F_QUEUE) {
2091 MSG_SET_TMP_FLAGS(msginfo->flags, MSG_QUEUED);
2092 } else if (item->stype == F_DRAFT) {
2093 MSG_SET_TMP_FLAGS(msginfo->flags, MSG_DRAFT);
2096 msginfo->folder = item;
2099 llast = newlist = g_slist_append(newlist, msginfo);
2101 llast = g_slist_append(llast, msginfo);
2102 llast = llast->next;
2106 g_string_free(str, TRUE);
2108 imap_seq_set_free(seq_list);
2110 session_set_access_time(SESSION(session));
2116 static GSList *imap_get_uncached_messages(IMAPSession *session,
2118 MsgNumberList *numlist)
2120 uncached_data *data = g_new0(uncached_data, 1);
2121 GSList *result = NULL;
2127 data->session = session;
2129 data->numlist = numlist;
2133 if (prefs_common.work_offline && !imap_gtk_should_override()) {
2138 #if (defined USE_PTHREAD && defined __GLIBC__ && (__GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 3)))
2139 if (pthread_create(&pt, PTHREAD_CREATE_JOINABLE,
2140 imap_get_uncached_messages_thread, data) != 0) {
2141 result = (GSList *)imap_get_uncached_messages_thread(data);
2145 debug_print("+++waiting for imap_get_uncached_messages_thread...\n");
2146 statusbar_print_all(_("IMAP4 Fetching uncached short headers..."));
2147 while(!data->done) {
2148 /* don't let the interface freeze while waiting */
2150 if (data->total != 0 && last_cur != data->cur && data->cur % 10 == 0) {
2152 g_snprintf(buf, sizeof(buf), "%d / %d",
2153 data->cur, data->total);
2154 gtk_progress_bar_set_text
2155 (GTK_PROGRESS_BAR(mainwindow_get_mainwindow()->progressbar), buf);
2156 gtk_progress_bar_set_fraction
2157 (GTK_PROGRESS_BAR(mainwindow_get_mainwindow()->progressbar),
2158 (gfloat)data->cur / (gfloat)data->total);
2159 last_cur = data->cur;
2162 gtk_progress_bar_set_fraction
2163 (GTK_PROGRESS_BAR(mainwindow_get_mainwindow()->progressbar), 0);
2164 gtk_progress_bar_set_text
2165 (GTK_PROGRESS_BAR(mainwindow_get_mainwindow()->progressbar), "");
2166 statusbar_pop_all();
2168 debug_print("---imap_get_uncached_messages_thread done\n");
2170 /* get the thread's return value and clean its resources */
2171 pthread_join(pt, (void *)&result);
2173 result = (GSList *)imap_get_uncached_messages_thread(data);
2179 static void imap_delete_all_cached_messages(FolderItem *item)
2183 g_return_if_fail(item != NULL);
2184 g_return_if_fail(item->folder != NULL);
2185 g_return_if_fail(FOLDER_CLASS(item->folder) == &imap_class);
2187 debug_print("Deleting all cached messages...\n");
2189 dir = folder_item_get_path(item);
2190 if (is_dir_exist(dir))
2191 remove_all_numbered_files(dir);
2194 debug_print("done.\n");
2198 static SockInfo *imap_open_tunnel(const gchar *server,
2199 const gchar *tunnelcmd,
2202 static SockInfo *imap_open_tunnel(const gchar *server,
2203 const gchar *tunnelcmd)
2208 if ((sock = sock_connect_cmd(server, tunnelcmd)) == NULL) {
2209 log_warning(_("Can't establish IMAP4 session with: %s\n"),
2214 return imap_init_sock(sock, ssl_type);
2216 return imap_init_sock(sock);
2220 void *imap_open_thread(void *data)
2222 SockInfo *sock = NULL;
2223 thread_data *td = (thread_data *)data;
2224 if ((sock = sock_connect(td->server, td->port)) == NULL) {
2225 log_warning(_("Can't connect to IMAP4 server: %s:%d\n"),
2226 td->server, td->port);
2236 static SockInfo *imap_open(const gchar *server, gushort port,
2239 static SockInfo *imap_open(const gchar *server, gushort port)
2242 thread_data *td = g_new0(thread_data, 1);
2246 SockInfo *sock = NULL;
2249 td->ssl_type = ssl_type;
2251 td->server = g_strdup(server);
2255 statusbar_print_all(_("Connecting to IMAP4 server: %s..."), server);
2257 #if (defined USE_PTHREAD && defined __GLIBC__ && (__GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 3)))
2258 if (pthread_create(&pt, PTHREAD_CREATE_JOINABLE,
2259 imap_open_thread, td) != 0) {
2260 statusbar_pop_all();
2261 sock = imap_open_thread(td);
2263 debug_print("+++waiting for imap_open_thread...\n");
2265 /* don't let the interface freeze while waiting */
2269 /* get the thread's return value and clean its resources */
2270 pthread_join(pt, (void *)&sock);
2273 sock = imap_open_thread(td);
2276 if (sock && td->ssl_type == SSL_TUNNEL && !ssl_init_socket(sock)) {
2277 log_warning(_("Can't establish IMAP4 session with: %s:%d\n"),
2278 td->server, td->port);
2287 debug_print("---imap_open_thread returned %p\n", sock);
2288 statusbar_pop_all();
2290 if(!sock && !prefs_common.no_recv_err_panel) {
2291 alertpanel_error(_("Can't connect to IMAP4 server: %s:%d"),
2299 static SockInfo *imap_init_sock(SockInfo *sock, SSLType ssl_type)
2301 static SockInfo *imap_init_sock(SockInfo *sock)
2308 static GList *imap_parse_namespace_str(gchar *str)
2313 IMAPNameSpace *namespace;
2314 GList *ns_list = NULL;
2316 while (*p != '\0') {
2317 /* parse ("#foo" "/") */
2319 while (*p && *p != '(') p++;
2320 if (*p == '\0') break;
2323 while (*p && *p != '"') p++;
2324 if (*p == '\0') break;
2328 while (*p && *p != '"') p++;
2329 if (*p == '\0') break;
2333 while (*p && isspace(*p)) p++;
2334 if (*p == '\0') break;
2335 if (strncmp(p, "NIL", 3) == 0)
2337 else if (*p == '"') {
2340 while (*p && *p != '"') p++;
2341 if (*p == '\0') break;
2346 while (*p && *p != ')') p++;
2347 if (*p == '\0') break;
2350 namespace = g_new(IMAPNameSpace, 1);
2351 namespace->name = g_strdup(name);
2352 namespace->separator = separator ? separator[0] : '\0';
2353 ns_list = g_list_append(ns_list, namespace);
2359 static void imap_parse_namespace(IMAPSession *session, IMAPFolder *folder)
2364 g_return_if_fail(session != NULL);
2365 g_return_if_fail(folder != NULL);
2367 if (folder->ns_personal != NULL ||
2368 folder->ns_others != NULL ||
2369 folder->ns_shared != NULL)
2372 if (!imap_has_capability(session, "NAMESPACE")) {
2373 imap_get_namespace_by_list(session, folder);
2377 if (imap_cmd_namespace(session, &ns_str)
2379 log_warning(_("can't get namespace\n"));
2383 str_array = strsplit_parenthesis(ns_str, '(', ')', 3);
2384 if (str_array == NULL) {
2386 imap_get_namespace_by_list(session, folder);
2390 folder->ns_personal = imap_parse_namespace_str(str_array[0]);
2391 if (str_array[0] && str_array[1])
2392 folder->ns_others = imap_parse_namespace_str(str_array[1]);
2393 if (str_array[0] && str_array[1] && str_array[2])
2394 folder->ns_shared = imap_parse_namespace_str(str_array[2]);
2395 g_strfreev(str_array);
2399 static void imap_get_namespace_by_list(IMAPSession *session, IMAPFolder *folder)
2401 GSList *item_list, *cur;
2402 gchar separator = '\0';
2403 IMAPNameSpace *namespace;
2405 g_return_if_fail(session != NULL);
2406 g_return_if_fail(folder != NULL);
2408 if (folder->ns_personal != NULL ||
2409 folder->ns_others != NULL ||
2410 folder->ns_shared != NULL)
2413 imap_gen_send(session, "LIST \"\" \"\"");
2414 item_list = imap_parse_list(folder, session, "", &separator);
2415 for (cur = item_list; cur != NULL; cur = cur->next)
2416 folder_item_destroy(FOLDER_ITEM(cur->data));
2417 g_slist_free(item_list);
2419 namespace = g_new(IMAPNameSpace, 1);
2420 namespace->name = g_strdup("");
2421 namespace->separator = separator;
2422 folder->ns_personal = g_list_append(NULL, namespace);
2425 static IMAPNameSpace *imap_find_namespace_from_list(GList *ns_list,
2428 IMAPNameSpace *namespace = NULL;
2429 gchar *tmp_path, *name;
2431 if (!path) path = "";
2433 for (; ns_list != NULL; ns_list = ns_list->next) {
2434 IMAPNameSpace *tmp_ns = ns_list->data;
2436 Xstrcat_a(tmp_path, path, "/", return namespace);
2437 Xstrdup_a(name, tmp_ns->name, return namespace);
2438 if (tmp_ns->separator && tmp_ns->separator != '/') {
2439 subst_char(tmp_path, tmp_ns->separator, '/');
2440 subst_char(name, tmp_ns->separator, '/');
2442 if (strncmp(tmp_path, name, strlen(name)) == 0)
2449 static IMAPNameSpace *imap_find_namespace(IMAPFolder *folder,
2452 IMAPNameSpace *namespace;
2454 g_return_val_if_fail(folder != NULL, NULL);
2456 namespace = imap_find_namespace_from_list(folder->ns_personal, path);
2457 if (namespace) return namespace;
2458 namespace = imap_find_namespace_from_list(folder->ns_others, path);
2459 if (namespace) return namespace;
2460 namespace = imap_find_namespace_from_list(folder->ns_shared, path);
2461 if (namespace) return namespace;
2466 static gchar imap_get_path_separator(IMAPFolder *folder, const gchar *path)
2468 IMAPNameSpace *namespace;
2469 gchar separator = '/';
2471 namespace = imap_find_namespace(folder, path);
2472 if (namespace && namespace->separator)
2473 separator = namespace->separator;
2478 static gchar *imap_get_real_path(IMAPFolder *folder, const gchar *path)
2483 g_return_val_if_fail(folder != NULL, NULL);
2484 g_return_val_if_fail(path != NULL, NULL);
2486 real_path = imap_utf8_to_modified_utf7(path);
2487 separator = imap_get_path_separator(folder, path);
2488 imap_path_separator_subst(real_path, separator);
2493 static gchar *imap_parse_atom(SockInfo *sock, gchar *src,
2494 gchar *dest, gint dest_len, GString *str)
2496 gchar *cur_pos = src;
2499 g_return_val_if_fail(str != NULL, cur_pos);
2501 /* read the next line if the current response buffer is empty */
2502 while (isspace(*(guchar *)cur_pos)) cur_pos++;
2503 while (*cur_pos == '\0') {
2504 if ((nextline = imap_getline(sock)) == NULL)
2506 g_string_assign(str, nextline);
2508 strretchomp(nextline);
2509 /* log_print("IMAP4< %s\n", nextline); */
2510 debug_print("IMAP4< %s\n", nextline);
2513 while (isspace(*(guchar *)cur_pos)) cur_pos++;
2516 if (!strncmp(cur_pos, "NIL", 3)) {
2519 } else if (*cur_pos == '\"') {
2522 p = get_quoted(cur_pos, '\"', dest, dest_len);
2523 cur_pos = p ? p : cur_pos + 2;
2524 } else if (*cur_pos == '{') {
2529 cur_pos = strchr_cpy(cur_pos + 1, '}', buf, sizeof(buf));
2531 g_return_val_if_fail(len >= 0, cur_pos);
2533 g_string_truncate(str, 0);
2537 if ((nextline = imap_getline(sock)) == NULL)
2539 line_len += strlen(nextline);
2540 g_string_append(str, nextline);
2542 strretchomp(nextline);
2543 /* log_print("IMAP4< %s\n", nextline); */
2544 debug_print("IMAP4< %s\n", nextline);
2546 } while (line_len < len);
2548 memcpy(dest, cur_pos, MIN(len, dest_len - 1));
2549 dest[MIN(len, dest_len - 1)] = '\0';
2556 static gchar *imap_get_header(SockInfo *sock, gchar *cur_pos, gchar **headers,
2566 g_return_val_if_fail(str != NULL, cur_pos);
2568 while (isspace(*(guchar *)cur_pos)) cur_pos++;
2570 g_return_val_if_fail(*cur_pos == '{', cur_pos);
2572 cur_pos = strchr_cpy(cur_pos + 1, '}', buf, sizeof(buf));
2574 g_return_val_if_fail(len >= 0, cur_pos);
2576 g_string_truncate(str, 0);
2580 if ((nextline = sock_getline(sock)) == NULL) {
2584 block_len += strlen(nextline);
2585 g_string_append(str, nextline);
2587 strretchomp(nextline);
2588 /* debug_print("IMAP4< %s\n", nextline); */
2590 } while (block_len < len);
2592 debug_print("IMAP4< [contents of BODY.PEEK[HEADER_FIELDS (...)]\n");
2594 *headers = g_strndup(cur_pos, len);
2597 while (isspace(*(guchar *)cur_pos)) cur_pos++;
2598 while (*cur_pos == '\0') {
2599 if ((nextline = sock_getline(sock)) == NULL)
2601 g_string_assign(str, nextline);
2603 strretchomp(nextline);
2604 debug_print("IMAP4< %s\n", nextline);
2607 while (isspace(*(guchar *)cur_pos)) cur_pos++;
2613 static MsgFlags imap_parse_flags(const gchar *flag_str)
2615 const gchar *p = flag_str;
2616 MsgFlags flags = {0, 0};
2618 flags.perm_flags = MSG_UNREAD;
2620 while ((p = strchr(p, '\\')) != NULL) {
2623 if (g_ascii_strncasecmp(p, "Recent", 6) == 0 && MSG_IS_UNREAD(flags)) {
2624 MSG_SET_PERM_FLAGS(flags, MSG_NEW);
2625 } else if (g_ascii_strncasecmp(p, "Seen", 4) == 0) {
2626 MSG_UNSET_PERM_FLAGS(flags, MSG_NEW|MSG_UNREAD);
2627 } else if (g_ascii_strncasecmp(p, "Deleted", 7) == 0) {
2628 MSG_SET_PERM_FLAGS(flags, MSG_DELETED);
2629 } else if (g_ascii_strncasecmp(p, "Flagged", 7) == 0) {
2630 MSG_SET_PERM_FLAGS(flags, MSG_MARKED);
2631 } else if (g_ascii_strncasecmp(p, "Answered", 8) == 0) {
2632 MSG_SET_PERM_FLAGS(flags, MSG_REPLIED);
2639 static MsgInfo *imap_parse_envelope(SockInfo *sock, FolderItem *item,
2642 gchar buf[IMAPBUFSIZE];
2643 MsgInfo *msginfo = NULL;
2648 MsgFlags flags = {0, 0}, imap_flags = {0, 0};
2650 g_return_val_if_fail(line_str != NULL, NULL);
2651 g_return_val_if_fail(line_str->str[0] == '*' &&
2652 line_str->str[1] == ' ', NULL);
2654 MSG_SET_TMP_FLAGS(flags, MSG_IMAP);
2655 if (item->stype == F_QUEUE) {
2656 MSG_SET_TMP_FLAGS(flags, MSG_QUEUED);
2657 } else if (item->stype == F_DRAFT) {
2658 MSG_SET_TMP_FLAGS(flags, MSG_DRAFT);
2661 cur_pos = line_str->str + 2;
2663 #define PARSE_ONE_ELEMENT(ch) \
2665 cur_pos = strchr_cpy(cur_pos, ch, buf, sizeof(buf)); \
2666 if (cur_pos == NULL) { \
2667 g_warning("cur_pos == NULL\n"); \
2668 procmsg_msginfo_free(msginfo); \
2673 PARSE_ONE_ELEMENT(' ');
2676 PARSE_ONE_ELEMENT(' ');
2677 g_return_val_if_fail(!strcmp(buf, "FETCH"), NULL);
2679 g_return_val_if_fail(*cur_pos == '(', NULL);
2682 while (*cur_pos != '\0' && *cur_pos != ')') {
2683 while (*cur_pos == ' ') cur_pos++;
2685 if (!strncmp(cur_pos, "UID ", 4)) {
2687 uid = strtoul(cur_pos, &cur_pos, 10);
2688 } else if (!strncmp(cur_pos, "FLAGS ", 6)) {
2690 if (*cur_pos != '(') {
2691 g_warning("*cur_pos != '('\n");
2692 procmsg_msginfo_free(msginfo);
2696 PARSE_ONE_ELEMENT(')');
2697 imap_flags = imap_parse_flags(buf);
2698 } else if (!strncmp(cur_pos, "RFC822.SIZE ", 12)) {
2700 size = strtol(cur_pos, &cur_pos, 10);
2701 } else if (!strncmp(cur_pos, "BODY[HEADER.FIELDS ", 19)) {
2705 if (*cur_pos != '(') {
2706 g_warning("*cur_pos != '('\n");
2707 procmsg_msginfo_free(msginfo);
2711 PARSE_ONE_ELEMENT(')');
2712 if (*cur_pos != ']') {
2713 g_warning("*cur_pos != ']'\n");
2714 procmsg_msginfo_free(msginfo);
2718 cur_pos = imap_get_header(sock, cur_pos, &headers,
2720 msginfo = procheader_parse_str(headers, flags, FALSE, FALSE);
2723 g_warning("invalid FETCH response: %s\n", cur_pos);
2729 msginfo->msgnum = uid;
2730 msginfo->size = size;
2731 msginfo->flags.tmp_flags |= imap_flags.tmp_flags;
2732 msginfo->flags.perm_flags = imap_flags.perm_flags;
2738 static gchar *imap_get_flag_str(IMAPFlags flags)
2743 str = g_string_new(NULL);
2745 if (IMAP_IS_SEEN(flags)) g_string_append(str, "\\Seen ");
2746 if (IMAP_IS_ANSWERED(flags)) g_string_append(str, "\\Answered ");
2747 if (IMAP_IS_FLAGGED(flags)) g_string_append(str, "\\Flagged ");
2748 if (IMAP_IS_DELETED(flags)) g_string_append(str, "\\Deleted ");
2749 if (IMAP_IS_DRAFT(flags)) g_string_append(str, "\\Draft");
2751 if (str->len > 0 && str->str[str->len - 1] == ' ')
2752 g_string_truncate(str, str->len - 1);
2755 g_string_free(str, FALSE);
2760 static gint imap_set_message_flags(IMAPSession *session,
2761 MsgNumberList *numlist,
2771 flag_str = imap_get_flag_str(flags);
2772 cmd = g_strconcat(is_set ? "+FLAGS.SILENT (" : "-FLAGS.SILENT (",
2773 flag_str, ")", NULL);
2776 seq_list = imap_get_seq_set_from_numlist(numlist);
2777 imapset = get_seq_set_from_seq_list(seq_list);
2779 ok = imap_cmd_store(session, imapset, cmd);
2782 imap_seq_set_free(seq_list);
2788 typedef struct _select_data {
2789 IMAPSession *session;
2794 guint32 *uid_validity;
2798 static void *imap_select_thread(void *data)
2800 select_data *stuff = (select_data *)data;
2801 IMAPSession *session = stuff->session;
2802 gchar *real_path = stuff->real_path;
2803 gint *exists = stuff->exists;
2804 gint *recent = stuff->recent;
2805 gint *unseen = stuff->unseen;
2806 guint32 *uid_validity = stuff->uid_validity;
2809 ok = imap_cmd_select(session, real_path,
2810 exists, recent, unseen, uid_validity, TRUE);
2812 return GINT_TO_POINTER(ok);
2815 static gint imap_select(IMAPSession *session, IMAPFolder *folder,
2817 gint *exists, gint *recent, gint *unseen,
2818 guint32 *uid_validity, gboolean block)
2822 gint exists_, recent_, unseen_;
2823 guint32 uid_validity_;
2825 if (!exists || !recent || !unseen || !uid_validity) {
2826 if (session->mbox && strcmp(session->mbox, path) == 0)
2827 return IMAP_SUCCESS;
2831 uid_validity = &uid_validity_;
2834 g_free(session->mbox);
2835 session->mbox = NULL;
2837 real_path = imap_get_real_path(folder, path);
2839 #if (defined USE_PTHREAD && defined __GLIBC__ && (__GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 3)))
2840 if (block == FALSE) {
2841 select_data *data = g_new0(select_data, 1);
2844 data->session = session;
2845 data->real_path = real_path;
2846 data->exists = exists;
2847 data->recent = recent;
2848 data->unseen = unseen;
2849 data->uid_validity = uid_validity;
2852 if (pthread_create(&pt, PTHREAD_CREATE_JOINABLE,
2853 imap_select_thread, data) != 0) {
2854 ok = GPOINTER_TO_INT(imap_select_thread(data));
2857 debug_print("+++waiting for imap_select_thread...\n");
2858 while(!data->done) {
2859 /* don't let the interface freeze while waiting */
2862 debug_print("---imap_select_thread done\n");
2864 /* get the thread's return value and clean its resources */
2865 pthread_join(pt, &tmp);
2866 ok = GPOINTER_TO_INT(tmp);
2870 ok = imap_cmd_select(session, real_path,
2871 exists, recent, unseen, uid_validity, block);
2874 ok = imap_cmd_select(session, real_path,
2875 exists, recent, unseen, uid_validity, block);
2877 if (ok != IMAP_SUCCESS)
2878 log_warning(_("can't select folder: %s\n"), real_path);
2880 session->mbox = g_strdup(path);
2881 session->folder_content_changed = FALSE;
2888 #define THROW(err) { ok = err; goto catch; }
2890 static gint imap_status(IMAPSession *session, IMAPFolder *folder,
2892 gint *messages, gint *recent,
2893 guint32 *uid_next, guint32 *uid_validity,
2894 gint *unseen, gboolean block)
2899 GPtrArray *argbuf = NULL;
2902 if (messages && recent && uid_next && uid_validity && unseen) {
2903 *messages = *recent = *uid_next = *uid_validity = *unseen = 0;
2904 argbuf = g_ptr_array_new();
2907 real_path = imap_get_real_path(folder, path);
2908 QUOTE_IF_REQUIRED(real_path_, real_path);
2909 imap_gen_send(session, "STATUS %s "
2910 "(MESSAGES RECENT UIDNEXT UIDVALIDITY UNSEEN)",
2913 ok = imap_cmd_ok_with_block(session, argbuf, block);
2914 if (ok != IMAP_SUCCESS || !argbuf) THROW(ok);
2916 str = search_array_str(argbuf, "STATUS");
2917 if (!str) THROW(IMAP_ERROR);
2919 str = strrchr(str, '(');
2920 if (!str) THROW(IMAP_ERROR);
2922 while (*str != '\0' && *str != ')') {
2923 while (*str == ' ') str++;
2925 if (!strncmp(str, "MESSAGES ", 9)) {
2927 *messages = strtol(str, &str, 10);
2928 } else if (!strncmp(str, "RECENT ", 7)) {
2930 *recent = strtol(str, &str, 10);
2931 } else if (!strncmp(str, "UIDNEXT ", 8)) {
2933 *uid_next = strtoul(str, &str, 10);
2934 } else if (!strncmp(str, "UIDVALIDITY ", 12)) {
2936 *uid_validity = strtoul(str, &str, 10);
2937 } else if (!strncmp(str, "UNSEEN ", 7)) {
2939 *unseen = strtol(str, &str, 10);
2941 g_warning("invalid STATUS response: %s\n", str);
2949 ptr_array_free_strings(argbuf);
2950 g_ptr_array_free(argbuf, TRUE);
2958 static gboolean imap_has_capability(IMAPSession *session, const gchar *cap)
2962 for (p = session->capability; *p != NULL; ++p) {
2963 if (!g_ascii_strcasecmp(*p, cap))
2970 static void imap_free_capabilities(IMAPSession *session)
2972 g_strfreev(session->capability);
2973 session->capability = NULL;
2976 /* low-level IMAP4rev1 commands */
2978 static gint imap_cmd_authenticate(IMAPSession *session, const gchar *user,
2979 const gchar *pass, IMAPAuthType type)
2986 gchar hexdigest[33];
2990 auth_type = "CRAM-MD5";
2992 imap_gen_send(session, "AUTHENTICATE %s", auth_type);
2993 ok = imap_gen_recv(session, &buf);
2994 if (ok != IMAP_SUCCESS || buf[0] != '+' || buf[1] != ' ') {
2999 challenge = g_malloc(strlen(buf + 2) + 1);
3000 challenge_len = base64_decode(challenge, buf + 2, -1);
3001 challenge[challenge_len] = '\0';
3003 log_print("IMAP< [Decoded: %s]\n", challenge);
3005 md5_hex_hmac(hexdigest, challenge, challenge_len, pass, strlen(pass));
3008 response = g_strdup_printf("%s %s", user, hexdigest);
3009 log_print("IMAP> [Encoded: %s]\n", response);
3010 response64 = g_malloc((strlen(response) + 3) * 2 + 1);
3011 base64_encode(response64, response, strlen(response));
3014 log_print("IMAP> %s\n", response64);
3015 sock_puts(SESSION(session)->sock, response64);
3016 ok = imap_cmd_ok(session, NULL);
3017 if (ok != IMAP_SUCCESS)
3018 log_warning(_("IMAP4 authentication failed.\n"));
3023 static gint imap_cmd_login(IMAPSession *session,
3024 const gchar *user, const gchar *pass)
3029 imap_gen_send(session, "LOGIN {%d}\r\n%s {%d}\r\n%s",
3031 strlen(pass), pass);
3033 ok = imap_gen_recv_with_block(session, &ans, TRUE);
3034 if (ok != IMAP_SUCCESS || ans[0] != '+' || ans[1] != ' ') {
3039 ok = imap_gen_recv_with_block(session, &ans, TRUE);
3040 if (ok != IMAP_SUCCESS || ans[0] != '+' || ans[1] != ' ') {
3045 ok = imap_cmd_ok(session, NULL);
3046 if (ok != IMAP_SUCCESS)
3047 log_warning(_("IMAP4 login failed.\n"));
3052 static gint imap_cmd_logout(IMAPSession *session)
3054 imap_gen_send(session, "LOGOUT");
3055 return imap_cmd_ok(session, NULL);
3058 static gint imap_cmd_noop(IMAPSession *session)
3060 imap_gen_send(session, "NOOP");
3061 return imap_cmd_ok(session, NULL);
3065 static gint imap_cmd_starttls(IMAPSession *session)
3067 imap_gen_send(session, "STARTTLS");
3068 return imap_cmd_ok(session, NULL);
3072 #define THROW(err) { ok = err; goto catch; }
3074 static gint imap_cmd_namespace(IMAPSession *session, gchar **ns_str)
3080 argbuf = g_ptr_array_new();
3082 imap_gen_send(session, "NAMESPACE");
3083 if ((ok = imap_cmd_ok(session, argbuf)) != IMAP_SUCCESS) THROW(ok);
3085 str = search_array_str(argbuf, "NAMESPACE");
3086 if (!str) THROW(IMAP_ERROR);
3088 *ns_str = g_strdup(str);
3091 ptr_array_free_strings(argbuf);
3092 g_ptr_array_free(argbuf, TRUE);
3099 static gint imap_cmd_list(IMAPSession *session, const gchar *ref,
3100 const gchar *mailbox, GPtrArray *argbuf)
3102 gchar *ref_, *mailbox_;
3104 if (!ref) ref = "\"\"";
3105 if (!mailbox) mailbox = "\"\"";
3107 QUOTE_IF_REQUIRED(ref_, ref);
3108 QUOTE_IF_REQUIRED(mailbox_, mailbox);
3109 imap_gen_send(session, "LIST %s %s", ref_, mailbox_);
3111 return imap_cmd_ok(session, argbuf);
3114 #define THROW goto catch
3116 static gint imap_cmd_do_select(IMAPSession *session, const gchar *folder,
3118 gint *exists, gint *recent, gint *unseen,
3119 guint32 *uid_validity, gboolean block)
3126 unsigned int uid_validity_;
3128 *exists = *recent = *unseen = *uid_validity = 0;
3129 argbuf = g_ptr_array_new();
3132 select_cmd = "EXAMINE";
3134 select_cmd = "SELECT";
3136 QUOTE_IF_REQUIRED(folder_, folder);
3137 imap_gen_send(session, "%s %s", select_cmd, folder_);
3139 if ((ok = imap_cmd_ok_with_block(session, argbuf, block)) != IMAP_SUCCESS) THROW;
3141 resp_str = search_array_contain_str(argbuf, "EXISTS");
3143 if (sscanf(resp_str,"%d EXISTS", exists) != 1) {
3144 g_warning("imap_cmd_select(): invalid EXISTS line.\n");
3149 resp_str = search_array_contain_str(argbuf, "RECENT");
3151 if (sscanf(resp_str, "%d RECENT", recent) != 1) {
3152 g_warning("imap_cmd_select(): invalid RECENT line.\n");
3157 resp_str = search_array_contain_str(argbuf, "UIDVALIDITY");
3159 if (sscanf(resp_str, "OK [UIDVALIDITY %u] ", &uid_validity_)
3161 g_warning("imap_cmd_select(): invalid UIDVALIDITY line.\n");
3164 *uid_validity = uid_validity_;
3167 resp_str = search_array_contain_str(argbuf, "UNSEEN");
3169 if (sscanf(resp_str, "OK [UNSEEN %d] ", unseen) != 1) {
3170 g_warning("imap_cmd_select(): invalid UNSEEN line.\n");
3176 ptr_array_free_strings(argbuf);
3177 g_ptr_array_free(argbuf, TRUE);
3182 static gint imap_cmd_select(IMAPSession *session, const gchar *folder,
3183 gint *exists, gint *recent, gint *unseen,
3184 guint32 *uid_validity, gboolean block)
3186 return imap_cmd_do_select(session, folder, FALSE,
3187 exists, recent, unseen, uid_validity, block);
3190 static gint imap_cmd_examine(IMAPSession *session, const gchar *folder,
3191 gint *exists, gint *recent, gint *unseen,
3192 guint32 *uid_validity, gboolean block)
3194 return imap_cmd_do_select(session, folder, TRUE,
3195 exists, recent, unseen, uid_validity, block);
3200 static gint imap_cmd_create(IMAPSession *session, const gchar *folder)
3204 QUOTE_IF_REQUIRED(folder_, folder);
3205 imap_gen_send(session, "CREATE %s", folder_);
3207 return imap_cmd_ok(session, NULL);
3210 static gint imap_cmd_rename(IMAPSession *session, const gchar *old_folder,
3211 const gchar *new_folder)
3213 gchar *old_folder_, *new_folder_;
3215 QUOTE_IF_REQUIRED(old_folder_, old_folder);
3216 QUOTE_IF_REQUIRED(new_folder_, new_folder);
3217 imap_gen_send(session, "RENAME %s %s", old_folder_, new_folder_);
3219 return imap_cmd_ok(session, NULL);
3222 static gint imap_cmd_delete(IMAPSession *session, const gchar *folder)
3226 QUOTE_IF_REQUIRED(folder_, folder);
3227 imap_gen_send(session, "DELETE %s", folder_);
3229 return imap_cmd_ok(session, NULL);
3232 static gint imap_cmd_search(IMAPSession *session, const gchar *criteria,
3233 GSList **list, gboolean block)
3239 g_return_val_if_fail(criteria != NULL, IMAP_ERROR);
3240 g_return_val_if_fail(list != NULL, IMAP_ERROR);
3244 argbuf = g_ptr_array_new();
3245 imap_gen_send(session, "UID SEARCH %s", criteria);
3247 ok = imap_cmd_ok_with_block(session, argbuf, block);
3248 if (ok != IMAP_SUCCESS) {
3249 ptr_array_free_strings(argbuf);
3250 g_ptr_array_free(argbuf, TRUE);
3254 if ((uidlist = search_array_str(argbuf, "SEARCH ")) != NULL) {
3255 gchar **strlist, **p;
3257 strlist = g_strsplit(uidlist + 7, " ", 0);
3258 for (p = strlist; *p != NULL; ++p) {
3261 if (sscanf(*p, "%u", &msgnum) == 1)
3262 *list = g_slist_append(*list, GINT_TO_POINTER(msgnum));
3264 g_strfreev(strlist);
3266 ptr_array_free_strings(argbuf);
3267 g_ptr_array_free(argbuf, TRUE);
3269 return IMAP_SUCCESS;
3272 typedef struct _fetch_data {
3273 IMAPSession *session;
3275 const gchar *filename;
3281 static void *imap_cmd_fetch_thread(void *data)
3283 fetch_data *stuff = (fetch_data *)data;
3284 IMAPSession *session = stuff->session;
3285 guint32 uid = stuff->uid;
3286 const gchar *filename = stuff->filename;
3294 if (filename == NULL) {
3296 return GINT_TO_POINTER(IMAP_ERROR);
3300 imap_gen_send(session, "UID FETCH %d BODY.PEEK[]", uid);
3302 imap_gen_send(session, "UID FETCH %d BODY.PEEK[HEADER]",
3304 while ((ok = imap_gen_recv_block(session, &buf)) == IMAP_SUCCESS) {
3305 if (buf[0] != '*' || buf[1] != ' ') {
3308 return GINT_TO_POINTER(IMAP_ERROR);
3310 if (strstr(buf, "FETCH") != NULL) break;
3313 if (ok != IMAP_SUCCESS) {
3316 return GINT_TO_POINTER(ok);
3319 #define RETURN_ERROR_IF_FAIL(cond) \
3322 stuff->done = TRUE; \
3323 return GINT_TO_POINTER(IMAP_ERROR); \
3326 cur_pos = strchr(buf, '{');
3327 RETURN_ERROR_IF_FAIL(cur_pos != NULL);
3328 cur_pos = strchr_cpy(cur_pos + 1, '}', size_str, sizeof(size_str));
3329 RETURN_ERROR_IF_FAIL(cur_pos != NULL);
3330 size_num = atol(size_str);
3331 RETURN_ERROR_IF_FAIL(size_num >= 0);
3333 RETURN_ERROR_IF_FAIL(*cur_pos == '\0');
3335 #undef RETURN_ERROR_IF_FAIL
3339 if (recv_bytes_write_to_file(SESSION(session)->sock,
3340 size_num, filename) != 0) {
3342 return GINT_TO_POINTER(IMAP_ERROR);
3344 if (imap_gen_recv_block(session, &buf) != IMAP_SUCCESS) {
3347 return GINT_TO_POINTER(IMAP_ERROR);
3350 if (buf[0] == '\0' || buf[strlen(buf) - 1] != ')') {
3353 return GINT_TO_POINTER(IMAP_ERROR);
3357 ok = imap_cmd_ok_block(session, NULL);
3360 return GINT_TO_POINTER(ok);
3363 static gint imap_cmd_fetch(IMAPSession *session, guint32 uid,
3364 const gchar *filename, gboolean headers,
3367 fetch_data *data = g_new0(fetch_data, 1);
3374 data->session = session;
3376 data->filename = filename;
3377 data->headers = headers;
3380 if (prefs_common.work_offline && !imap_gtk_should_override()) {
3385 #if (defined USE_PTHREAD && defined __GLIBC__ && (__GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 3)))
3386 if (pthread_create(&pt, PTHREAD_CREATE_JOINABLE,
3387 imap_cmd_fetch_thread, data) != 0) {
3388 result = GPOINTER_TO_INT(imap_cmd_fetch_thread(data));
3392 debug_print("+++waiting for imap_cmd_fetch_thread...\n");
3393 while(!data->done) {
3394 /* don't let the interface freeze while waiting */
3397 debug_print("---imap_cmd_fetch_thread done\n");
3399 /* get the thread's return value and clean its resources */
3400 pthread_join(pt, &tmp);
3401 result = GPOINTER_TO_INT(tmp);
3403 result = GPOINTER_TO_INT(imap_cmd_fetch_thread(data));
3409 static gint imap_cmd_append(IMAPSession *session, const gchar *destfolder,
3410 const gchar *file, IMAPFlags flags,
3417 unsigned int new_uid_;
3419 gchar buf[BUFFSIZE];
3424 g_return_val_if_fail(file != NULL, IMAP_ERROR);
3426 size = get_file_size_as_crlf(file);
3427 if ((fp = fopen(file, "rb")) == NULL) {
3428 FILE_OP_ERROR(file, "fopen");
3431 QUOTE_IF_REQUIRED(destfolder_, destfolder);
3432 flag_str = imap_get_flag_str(flags);
3433 imap_gen_send(session, "APPEND %s (%s) {%d}",
3434 destfolder_, flag_str, size);
3437 ok = imap_gen_recv(session, &ret);
3438 if (ok != IMAP_SUCCESS || ret[0] != '+' || ret[1] != ' ') {
3439 log_warning(_("can't append %s to %s\n"), file, destfolder_);
3446 log_print("IMAP4> %s\n", "(sending file...)");
3448 while (fgets(buf, sizeof(buf), fp) != NULL) {
3450 if (sock_puts(SESSION(session)->sock, buf) < 0) {
3457 FILE_OP_ERROR(file, "fgets");
3462 sock_puts(SESSION(session)->sock, "");
3466 if (new_uid != NULL)
3469 if (new_uid != NULL && session->uidplus) {
3470 argbuf = g_ptr_array_new();
3472 ok = imap_cmd_ok(session, argbuf);
3473 if ((ok == IMAP_SUCCESS) && (argbuf->len > 0)) {
3474 resp_str = g_ptr_array_index(argbuf, argbuf->len - 1);
3476 sscanf(resp_str, "%*u OK [APPENDUID %*u %u]",
3478 *new_uid = new_uid_;
3482 ptr_array_free_strings(argbuf);
3483 g_ptr_array_free(argbuf, TRUE);
3485 ok = imap_cmd_ok(session, NULL);
3487 if (ok != IMAP_SUCCESS)
3488 log_warning(_("can't append message to %s\n"),
3494 static MsgNumberList *imapset_to_numlist(IMAPSet imapset)
3496 gchar **ranges, **range;
3497 unsigned int low, high;
3498 MsgNumberList *uids = NULL;
3500 ranges = g_strsplit(imapset, ",", 0);
3501 for (range = ranges; *range != NULL; range++) {
3502 if (sscanf(*range, "%u:%u", &low, &high) == 1)
3503 uids = g_slist_prepend(uids, GINT_TO_POINTER(low));
3506 for (i = low; i <= high; i++)
3507 uids = g_slist_prepend(uids, GINT_TO_POINTER(i));
3510 uids = g_slist_reverse(uids);
3516 static gint imap_cmd_copy(IMAPSession *session, const gchar *seq_set,
3517 const gchar *destfolder, GRelation *uid_mapping)
3522 g_return_val_if_fail(session != NULL, IMAP_ERROR);
3523 g_return_val_if_fail(seq_set != NULL, IMAP_ERROR);
3524 g_return_val_if_fail(destfolder != NULL, IMAP_ERROR);
3526 QUOTE_IF_REQUIRED(destfolder_, destfolder);
3527 imap_gen_send(session, "UID COPY %s %s", seq_set, destfolder_);
3529 if (uid_mapping != NULL && session->uidplus) {
3531 gchar *resp_str = NULL, *olduids_str, *newuids_str;
3532 MsgNumberList *olduids, *old_cur, *newuids, *new_cur;
3534 reply = g_ptr_array_new();
3535 ok = imap_cmd_ok(session, reply);
3536 if ((ok == IMAP_SUCCESS) && (reply->len > 0)) {
3537 resp_str = g_ptr_array_index(reply, reply->len - 1);
3539 olduids_str = g_new0(gchar, strlen(resp_str));
3540 newuids_str = g_new0(gchar, strlen(resp_str));
3541 if (sscanf(resp_str, "%*s OK [COPYUID %*u %[0-9,:] %[0-9,:]]",
3542 olduids_str, newuids_str) == 2) {
3543 olduids = imapset_to_numlist(olduids_str);
3544 newuids = imapset_to_numlist(newuids_str);
3548 while(old_cur != NULL && new_cur != NULL) {
3549 g_relation_insert(uid_mapping,
3550 GPOINTER_TO_INT(old_cur->data),
3551 GPOINTER_TO_INT(new_cur->data));
3552 old_cur = g_slist_next(old_cur);
3553 new_cur = g_slist_next(new_cur);
3556 g_slist_free(olduids);
3557 g_slist_free(newuids);
3559 g_free(olduids_str);
3560 g_free(newuids_str);
3563 ptr_array_free_strings(reply);
3564 g_ptr_array_free(reply, TRUE);
3566 ok = imap_cmd_ok(session, NULL);
3568 if (ok != IMAP_SUCCESS)
3569 log_warning(_("can't copy %s to %s\n"), seq_set, destfolder_);
3574 gint imap_cmd_envelope(IMAPSession *session, IMAPSet set)
3576 static gchar *header_fields =
3577 "Date From To Cc Subject Message-ID References In-Reply-To" ;
3580 (session, "UID FETCH %s (UID FLAGS RFC822.SIZE BODY.PEEK[HEADER.FIELDS (%s)])",
3581 set, header_fields);
3583 return IMAP_SUCCESS;
3586 static gint imap_cmd_store(IMAPSession *session, IMAPSet seq_set,
3591 imap_gen_send(session, "UID STORE %s %s", seq_set, sub_cmd);
3593 if ((ok = imap_cmd_ok(session, NULL)) != IMAP_SUCCESS) {
3594 log_warning(_("error while imap command: STORE %s %s\n"),
3599 return IMAP_SUCCESS;
3602 typedef struct _expunge_data {
3603 IMAPSession *session;
3608 static void *imap_cmd_expunge_thread(void *data)
3610 expunge_data *stuff = (expunge_data *)data;
3611 IMAPSession *session = stuff->session;
3612 IMAPSet seq_set = stuff->seq_set;
3616 if (seq_set && session->uidplus)
3617 imap_gen_send(session, "UID EXPUNGE %s", seq_set);
3619 imap_gen_send(session, "EXPUNGE");
3620 if ((ok = imap_cmd_ok_with_block(session, NULL, TRUE)) != IMAP_SUCCESS) {
3621 log_warning(_("error while imap command: EXPUNGE\n"));
3623 return GINT_TO_POINTER(ok);
3627 return GINT_TO_POINTER(IMAP_SUCCESS);
3630 static gint imap_cmd_expunge(IMAPSession *session, IMAPSet seq_set)
3632 expunge_data *data = g_new0(expunge_data, 1);
3639 data->session = session;
3640 data->seq_set = seq_set;
3642 if (prefs_common.work_offline && !imap_gtk_should_override()) {
3647 #if (defined USE_PTHREAD && defined __GLIBC__ && (__GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 3)))
3648 if (pthread_create(&pt, PTHREAD_CREATE_JOINABLE,
3649 imap_cmd_expunge_thread, data) != 0) {
3650 result = GPOINTER_TO_INT(imap_cmd_expunge_thread(data));
3654 debug_print("+++waiting for imap_cmd_expunge_thread...\n");
3655 while(!data->done) {
3656 /* don't let the interface freeze while waiting */
3659 debug_print("---imap_cmd_expunge_thread done\n");
3661 /* get the thread's return value and clean its resources */
3662 pthread_join(pt, &tmp);
3663 result = GPOINTER_TO_INT(tmp);
3665 result = GPOINTER_TO_INT(imap_cmd_expunge_thread(data));
3671 static gint imap_cmd_close(IMAPSession *session)
3675 imap_gen_send(session, "CLOSE");
3676 if ((ok = imap_cmd_ok(session, NULL)) != IMAP_SUCCESS)
3677 log_warning(_("error while imap command: CLOSE\n"));
3682 static gint imap_cmd_ok_with_block(IMAPSession *session, GPtrArray *argbuf, gboolean block)
3684 gint ok = IMAP_SUCCESS;
3689 while ((ok = imap_gen_recv_with_block(session, &buf, block))
3691 /* make sure data is long enough for any substring of buf */
3692 data = alloca(strlen(buf) + 1);
3694 /* untagged line read */
3695 if (buf[0] == '*' && buf[1] == ' ') {
3698 g_ptr_array_add(argbuf, g_strdup(buf + 2));
3700 if (sscanf(buf + 2, "%d %s", &num, data) >= 2) {
3701 if (!strcmp(data, "EXISTS")) {
3702 session->exists = num;
3703 session->folder_content_changed = TRUE;
3706 if(!strcmp(data, "EXPUNGE")) {
3708 session->folder_content_changed = TRUE;
3711 /* tagged line with correct tag and OK response found */
3712 } else if ((sscanf(buf, "%d %s", &cmd_num, data) >= 2) &&
3713 (cmd_num == session->cmd_count) &&
3714 !strcmp(data, "OK")) {
3716 g_ptr_array_add(argbuf, g_strdup(buf));
3718 /* everything else */
3729 static gint imap_cmd_ok(IMAPSession *session, GPtrArray *argbuf)
3731 return imap_cmd_ok_with_block(session, argbuf, FALSE);
3733 static gint imap_cmd_ok_block(IMAPSession *session, GPtrArray *argbuf)
3735 return imap_cmd_ok_with_block(session, argbuf, TRUE);
3737 static void imap_gen_send(IMAPSession *session, const gchar *format, ...)
3744 va_start(args, format);
3745 tmp = g_strdup_vprintf(format, args);
3748 session->cmd_count++;
3750 buf = g_strdup_printf("%d %s\r\n", session->cmd_count, tmp);
3751 if (!g_ascii_strncasecmp(tmp, "LOGIN ", 6) && (p = strchr(tmp + 6, ' '))) {
3753 log_print("IMAP4> %d %s ********\n", session->cmd_count, tmp);
3755 log_print("IMAP4> %d %s\n", session->cmd_count, tmp);
3757 sock_write_all(SESSION(session)->sock, buf, strlen(buf));
3762 static gint imap_gen_recv_with_block(IMAPSession *session, gchar **ret, gboolean block)
3765 if ((*ret = imap_getline(SESSION(session)->sock)) == NULL)
3768 if ((*ret = sock_getline(SESSION(session)->sock)) == NULL)
3773 log_print("IMAP4< %s\n", *ret);
3775 session_set_access_time(SESSION(session));
3777 return IMAP_SUCCESS;
3780 static gint imap_gen_recv_block(IMAPSession *session, gchar **ret)
3782 return imap_gen_recv_with_block(session, ret, TRUE);
3785 static gint imap_gen_recv(IMAPSession *session, gchar **ret)
3787 return imap_gen_recv_with_block(session, ret, FALSE);
3789 /* misc utility functions */
3791 static gchar *strchr_cpy(const gchar *src, gchar ch, gchar *dest, gint len)
3796 tmp = strchr(src, ch);
3800 memcpy(dest, src, MIN(tmp - src, len - 1));
3801 dest[MIN(tmp - src, len - 1)] = '\0';
3806 static gchar *get_quoted(const gchar *src, gchar ch, gchar *dest, gint len)
3808 const gchar *p = src;
3811 g_return_val_if_fail(*p == ch, NULL);
3816 while (*p != '\0' && *p != ch) {
3818 if (*p == '\\' && *(p + 1) != '\0')
3827 return (gchar *)(*p == ch ? p + 1 : p);
3830 static gchar *search_array_contain_str(GPtrArray *array, const gchar *str)
3834 for (i = 0; i < array->len; i++) {
3837 tmp = g_ptr_array_index(array, i);
3838 if (strstr(tmp, str) != NULL)
3845 static gchar *search_array_str(GPtrArray *array, const gchar *str)
3852 for (i = 0; i < array->len; i++) {
3855 tmp = g_ptr_array_index(array, i);
3856 if (!strncmp(tmp, str, len))
3863 static void imap_path_separator_subst(gchar *str, gchar separator)
3866 gboolean in_escape = FALSE;
3868 if (!separator || separator == '/') return;
3870 for (p = str; *p != '\0'; p++) {
3871 if (*p == '/' && !in_escape)
3873 else if (*p == '&' && *(p + 1) != '-' && !in_escape)
3875 else if (*p == '-' && in_escape)
3880 static gchar *imap_modified_utf7_to_utf8(const gchar *mutf7_str)
3882 static iconv_t cd = (iconv_t)-1;
3883 static gboolean iconv_ok = TRUE;
3886 size_t norm_utf7_len;
3888 gchar *to_str, *to_p;
3890 gboolean in_escape = FALSE;
3892 if (!iconv_ok) return g_strdup(mutf7_str);
3894 if (cd == (iconv_t)-1) {
3895 cd = iconv_open(CS_INTERNAL, CS_UTF_7);
3896 if (cd == (iconv_t)-1) {
3897 g_warning("iconv cannot convert UTF-7 to %s\n",
3900 return g_strdup(mutf7_str);
3904 /* modified UTF-7 to normal UTF-7 conversion */
3905 norm_utf7 = g_string_new(NULL);
3907 for (p = mutf7_str; *p != '\0'; p++) {
3908 /* replace: '&' -> '+',
3910 escaped ',' -> '/' */
3911 if (!in_escape && *p == '&') {
3912 if (*(p + 1) != '-') {
3913 g_string_append_c(norm_utf7, '+');
3916 g_string_append_c(norm_utf7, '&');
3919 } else if (in_escape && *p == ',') {
3920 g_string_append_c(norm_utf7, '/');
3921 } else if (in_escape && *p == '-') {
3922 g_string_append_c(norm_utf7, '-');
3925 g_string_append_c(norm_utf7, *p);
3929 norm_utf7_p = norm_utf7->str;
3930 norm_utf7_len = norm_utf7->len;
3931 to_len = strlen(mutf7_str) * 5;
3932 to_p = to_str = g_malloc(to_len + 1);
3934 if (iconv(cd, (ICONV_CONST gchar **)&norm_utf7_p, &norm_utf7_len,
3935 &to_p, &to_len) == -1) {
3936 g_warning(_("iconv cannot convert UTF-7 to %s\n"),
3937 conv_get_locale_charset_str());
3938 g_string_free(norm_utf7, TRUE);
3940 return g_strdup(mutf7_str);
3943 /* second iconv() call for flushing */
3944 iconv(cd, NULL, NULL, &to_p, &to_len);
3945 g_string_free(norm_utf7, TRUE);
3951 static gchar *imap_utf8_to_modified_utf7(const gchar *from)
3953 static iconv_t cd = (iconv_t)-1;
3954 static gboolean iconv_ok = TRUE;
3955 gchar *norm_utf7, *norm_utf7_p;
3956 size_t from_len, norm_utf7_len;
3958 gchar *from_tmp, *to, *p;
3959 gboolean in_escape = FALSE;
3961 if (!iconv_ok) return g_strdup(from);
3963 if (cd == (iconv_t)-1) {
3964 cd = iconv_open(CS_UTF_7, CS_INTERNAL);
3965 if (cd == (iconv_t)-1) {
3966 g_warning(_("iconv cannot convert %s to UTF-7\n"),
3969 return g_strdup(from);
3973 /* UTF-8 to normal UTF-7 conversion */
3974 Xstrdup_a(from_tmp, from, return g_strdup(from));
3975 from_len = strlen(from);
3976 norm_utf7_len = from_len * 5;
3977 Xalloca(norm_utf7, norm_utf7_len + 1, return g_strdup(from));
3978 norm_utf7_p = norm_utf7;
3980 #define IS_PRINT(ch) (isprint(ch) && IS_ASCII(ch))
3982 while (from_len > 0) {
3983 if (*from_tmp == '+') {
3984 *norm_utf7_p++ = '+';
3985 *norm_utf7_p++ = '-';
3989 } else if (IS_PRINT(*(guchar *)from_tmp)) {
3990 /* printable ascii char */
3991 *norm_utf7_p = *from_tmp;
3997 size_t conv_len = 0;
3999 /* unprintable char: convert to UTF-7 */
4001 while (!IS_PRINT(*(guchar *)p) && conv_len < from_len) {
4002 conv_len += g_utf8_skip[*(guchar *)p];
4003 p += g_utf8_skip[*(guchar *)p];
4006 from_len -= conv_len;
4007 if (iconv(cd, (ICONV_CONST gchar **)&from_tmp,
4009 &norm_utf7_p, &norm_utf7_len) == -1) {
4010 g_warning(_("iconv cannot convert UTF-8 to UTF-7\n"));
4011 return g_strdup(from);
4014 /* second iconv() call for flushing */
4015 iconv(cd, NULL, NULL, &norm_utf7_p, &norm_utf7_len);
4021 *norm_utf7_p = '\0';
4022 to_str = g_string_new(NULL);
4023 for (p = norm_utf7; p < norm_utf7_p; p++) {
4024 /* replace: '&' -> "&-",
4027 BASE64 '/' -> ',' */
4028 if (!in_escape && *p == '&') {
4029 g_string_append(to_str, "&-");
4030 } else if (!in_escape && *p == '+') {
4031 if (*(p + 1) == '-') {
4032 g_string_append_c(to_str, '+');
4035 g_string_append_c(to_str, '&');
4038 } else if (in_escape && *p == '/') {
4039 g_string_append_c(to_str, ',');
4040 } else if (in_escape && *p == '-') {
4041 g_string_append_c(to_str, '-');
4044 g_string_append_c(to_str, *p);
4050 g_string_append_c(to_str, '-');
4054 g_string_free(to_str, FALSE);
4059 static GSList *imap_get_seq_set_from_numlist(MsgNumberList *numlist)
4062 GSList *sorted_list, *cur;
4063 guint first, last, next;
4065 GSList *ret_list = NULL;
4067 if (numlist == NULL)
4070 str = g_string_sized_new(256);
4072 sorted_list = g_slist_copy(numlist);
4073 sorted_list = g_slist_sort(sorted_list, g_int_compare);
4075 first = GPOINTER_TO_INT(sorted_list->data);
4077 for (cur = sorted_list; cur != NULL; cur = g_slist_next(cur)) {
4078 if (GPOINTER_TO_INT(cur->data) == 0)
4081 last = GPOINTER_TO_INT(cur->data);
4083 next = GPOINTER_TO_INT(cur->next->data);
4087 if (last + 1 != next || next == 0) {
4089 g_string_append_c(str, ',');
4091 g_string_append_printf(str, "%u", first);
4093 g_string_append_printf(str, "%u:%u", first, last);
4097 if (str->len > IMAP_CMD_LIMIT) {
4098 ret_str = g_strdup(str->str);
4099 ret_list = g_slist_append(ret_list, ret_str);
4100 g_string_truncate(str, 0);
4106 ret_str = g_strdup(str->str);
4107 ret_list = g_slist_append(ret_list, ret_str);
4110 g_slist_free(sorted_list);
4111 g_string_free(str, TRUE);
4116 static GSList *imap_get_seq_set_from_msglist(MsgInfoList *msglist)
4118 MsgNumberList *numlist = NULL;
4122 for (cur = msglist; cur != NULL; cur = g_slist_next(cur)) {
4123 MsgInfo *msginfo = (MsgInfo *) cur->data;
4125 numlist = g_slist_append(numlist, GINT_TO_POINTER(msginfo->msgnum));
4127 seq_list = imap_get_seq_set_from_numlist(numlist);
4128 g_slist_free(numlist);
4133 static void imap_seq_set_free(GSList *seq_list)
4135 slist_free_strings(seq_list);
4136 g_slist_free(seq_list);
4140 static gboolean imap_rename_folder_func(GNode *node, gpointer data)
4142 FolderItem *item = node->data;
4143 gchar **paths = data;
4144 const gchar *oldpath = paths[0];
4145 const gchar *newpath = paths[1];
4147 gchar *new_itempath;
4150 oldpathlen = strlen(oldpath);
4151 if (strncmp(oldpath, item->path, oldpathlen) != 0) {
4152 g_warning("path doesn't match: %s, %s\n", oldpath, item->path);
4156 base = item->path + oldpathlen;
4157 while (*base == G_DIR_SEPARATOR) base++;
4159 new_itempath = g_strdup(newpath);
4161 new_itempath = g_strconcat(newpath, G_DIR_SEPARATOR_S, base,
4164 item->path = new_itempath;
4169 typedef struct _get_list_uid_data {
4171 IMAPFolderItem *item;
4172 GSList **msgnum_list;
4174 } get_list_uid_data;
4176 static void *get_list_of_uids_thread(void *data)
4178 get_list_uid_data *stuff = (get_list_uid_data *)data;
4179 Folder *folder = stuff->folder;
4180 IMAPFolderItem *item = stuff->item;
4181 GSList **msgnum_list = stuff->msgnum_list;
4182 gint ok, nummsgs = 0, lastuid_old;
4183 IMAPSession *session;
4184 GSList *uidlist, *elem;
4187 session = imap_session_get(folder);
4188 if (session == NULL) {
4190 return GINT_TO_POINTER(-1);
4193 ok = imap_select(session, IMAP_FOLDER(folder), item->item.path,
4194 NULL, NULL, NULL, NULL, TRUE);
4195 if (ok != IMAP_SUCCESS) {
4197 return GINT_TO_POINTER(-1);
4200 cmd_buf = g_strdup_printf("UID %d:*", item->lastuid + 1);
4201 ok = imap_cmd_search(session, cmd_buf, &uidlist, TRUE);
4204 if (ok == IMAP_SOCKET) {
4205 session_destroy((Session *)session);
4206 ((RemoteFolder *)folder)->session = NULL;
4208 return GINT_TO_POINTER(-1);
4211 if (ok != IMAP_SUCCESS) {
4215 argbuf = g_ptr_array_new();
4217 cmd_buf = g_strdup_printf("UID FETCH %d:* (UID)", item->lastuid + 1);
4218 imap_gen_send(session, cmd_buf);
4220 ok = imap_cmd_ok_block(session, argbuf);
4221 if (ok != IMAP_SUCCESS) {
4222 ptr_array_free_strings(argbuf);
4223 g_ptr_array_free(argbuf, TRUE);
4225 return GINT_TO_POINTER(-1);
4228 for(i = 0; i < argbuf->len; i++) {
4231 if((ret = sscanf(g_ptr_array_index(argbuf, i),
4232 "%*d FETCH (UID %d)", &msgnum)) == 1)
4233 uidlist = g_slist_prepend(uidlist, GINT_TO_POINTER(msgnum));
4235 ptr_array_free_strings(argbuf);
4236 g_ptr_array_free(argbuf, TRUE);
4239 lastuid_old = item->lastuid;
4240 *msgnum_list = g_slist_copy(item->uid_list);
4241 nummsgs = g_slist_length(*msgnum_list);
4242 debug_print("Got %d uids from cache\n", g_slist_length(item->uid_list));
4244 for (elem = uidlist; elem != NULL; elem = g_slist_next(elem)) {
4247 msgnum = GPOINTER_TO_INT(elem->data);
4248 if (msgnum > lastuid_old) {
4249 *msgnum_list = g_slist_prepend(*msgnum_list, GINT_TO_POINTER(msgnum));
4250 item->uid_list = g_slist_prepend(item->uid_list, GINT_TO_POINTER(msgnum));
4253 if(msgnum > item->lastuid)
4254 item->lastuid = msgnum;
4257 g_slist_free(uidlist);
4260 return GINT_TO_POINTER(nummsgs);
4263 static gint get_list_of_uids(Folder *folder, IMAPFolderItem *item, GSList **msgnum_list)
4266 get_list_uid_data *data = g_new0(get_list_uid_data, 1);
4272 data->folder = folder;
4274 data->msgnum_list = msgnum_list;
4276 if (prefs_common.work_offline && !imap_gtk_should_override()) {
4281 #if (defined USE_PTHREAD && defined __GLIBC__ && (__GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 3)))
4282 if (pthread_create(&pt, PTHREAD_CREATE_JOINABLE,
4283 get_list_of_uids_thread, data) != 0) {
4284 result = GPOINTER_TO_INT(get_list_of_uids_thread(data));
4288 debug_print("+++waiting for get_list_of_uids_thread...\n");
4289 while(!data->done) {
4290 /* don't let the interface freeze while waiting */
4293 debug_print("---get_list_of_uids_thread done\n");
4295 /* get the thread's return value and clean its resources */
4296 pthread_join(pt, &tmp);
4297 result = GPOINTER_TO_INT(tmp);
4299 result = GPOINTER_TO_INT(get_list_of_uids_thread(data));
4306 gint imap_get_num_list(Folder *folder, FolderItem *_item, GSList **msgnum_list, gboolean *old_uids_valid)
4308 IMAPFolderItem *item = (IMAPFolderItem *)_item;
4309 IMAPSession *session;
4310 gint ok, nummsgs = 0, exists, recent, uid_val, uid_next, unseen;
4311 GSList *uidlist = NULL;
4313 gboolean selected_folder;
4315 g_return_val_if_fail(folder != NULL, -1);
4316 g_return_val_if_fail(item != NULL, -1);
4317 g_return_val_if_fail(item->item.path != NULL, -1);
4318 g_return_val_if_fail(FOLDER_CLASS(folder) == &imap_class, -1);
4319 g_return_val_if_fail(folder->account != NULL, -1);
4321 session = imap_session_get(folder);
4322 g_return_val_if_fail(session != NULL, -1);
4324 selected_folder = (session->mbox != NULL) &&
4325 (!strcmp(session->mbox, item->item.path));
4326 if (selected_folder) {
4327 ok = imap_cmd_noop(session);
4328 if (ok != IMAP_SUCCESS)
4330 exists = session->exists;
4332 *old_uids_valid = TRUE;
4334 ok = imap_status(session, IMAP_FOLDER(folder), item->item.path,
4335 &exists, &recent, &uid_next, &uid_val, &unseen, FALSE);
4336 if (ok != IMAP_SUCCESS)
4339 if(item->item.mtime == uid_val)
4340 *old_uids_valid = TRUE;
4342 *old_uids_valid = FALSE;
4344 debug_print("Freeing imap uid cache\n");
4346 g_slist_free(item->uid_list);
4347 item->uid_list = NULL;
4349 item->item.mtime = uid_val;
4351 imap_delete_all_cached_messages((FolderItem *)item);
4355 if (!selected_folder)
4356 item->uid_next = uid_next;
4358 /* If old uid_next matches new uid_next we can be sure no message
4359 was added to the folder */
4360 if (( selected_folder && !session->folder_content_changed) ||
4361 (!selected_folder && uid_next == item->uid_next)) {
4362 nummsgs = g_slist_length(item->uid_list);
4364 /* If number of messages is still the same we
4365 know our caches message numbers are still valid,
4366 otherwise if the number of messages has decrease
4367 we discard our cache to start a new scan to find
4368 out which numbers have been removed */
4369 if (exists == nummsgs) {
4370 *msgnum_list = g_slist_copy(item->uid_list);
4372 } else if (exists < nummsgs) {
4373 debug_print("Freeing imap uid cache");
4375 g_slist_free(item->uid_list);
4376 item->uid_list = NULL;
4381 *msgnum_list = NULL;
4385 nummsgs = get_list_of_uids(folder, item, &uidlist);
4387 if (nummsgs != exists) {
4388 /* Cache contains more messages then folder, we have cached
4389 an old UID of a message that was removed and new messages
4390 have been added too, otherwise the uid_next check would
4392 debug_print("Freeing imap uid cache");
4394 g_slist_free(item->uid_list);
4395 item->uid_list = NULL;
4397 g_slist_free(*msgnum_list);
4399 nummsgs = get_list_of_uids(folder, item, &uidlist);
4402 *msgnum_list = uidlist;
4404 dir = folder_item_get_path((FolderItem *)item);
4405 debug_print("removing old messages from %s\n", dir);
4406 remove_numbered_files_not_in_list(dir, *msgnum_list);
4412 static MsgInfo *imap_parse_msg(const gchar *file, FolderItem *item)
4417 flags.perm_flags = MSG_NEW|MSG_UNREAD;
4418 flags.tmp_flags = 0;
4420 g_return_val_if_fail(item != NULL, NULL);
4421 g_return_val_if_fail(file != NULL, NULL);
4423 if (item->stype == F_QUEUE) {
4424 MSG_SET_TMP_FLAGS(flags, MSG_QUEUED);
4425 } else if (item->stype == F_DRAFT) {
4426 MSG_SET_TMP_FLAGS(flags, MSG_DRAFT);
4429 msginfo = procheader_parse_file(file, flags, FALSE, FALSE);
4430 if (!msginfo) return NULL;
4432 msginfo->plaintext_file = g_strdup(file);
4433 msginfo->folder = item;
4438 GSList *imap_get_msginfos(Folder *folder, FolderItem *item, GSList *msgnum_list)
4440 IMAPSession *session;
4441 MsgInfoList *ret = NULL;
4444 g_return_val_if_fail(folder != NULL, NULL);
4445 g_return_val_if_fail(item != NULL, NULL);
4446 g_return_val_if_fail(msgnum_list != NULL, NULL);
4448 session = imap_session_get(folder);
4449 g_return_val_if_fail(session != NULL, NULL);
4451 debug_print("IMAP getting msginfos\n");
4452 ok = imap_select(session, IMAP_FOLDER(folder), item->path,
4453 NULL, NULL, NULL, NULL, FALSE);
4454 if (ok != IMAP_SUCCESS)
4457 if (!(item->stype == F_QUEUE || item->stype == F_DRAFT)) {
4458 ret = g_slist_concat(ret,
4459 imap_get_uncached_messages(
4460 session, item, msgnum_list));
4462 MsgNumberList *sorted_list, *elem;
4463 gint startnum, lastnum;
4465 sorted_list = g_slist_sort(g_slist_copy(msgnum_list), g_int_compare);
4467 startnum = lastnum = GPOINTER_TO_INT(sorted_list->data);
4469 for (elem = sorted_list;; elem = g_slist_next(elem)) {
4473 num = GPOINTER_TO_INT(elem->data);
4475 if (num > lastnum + 1 || elem == NULL) {
4477 for (i = startnum; i <= lastnum; ++i) {
4480 file = imap_fetch_msg(folder, item, i);
4482 MsgInfo *msginfo = imap_parse_msg(file, item);
4483 if (msginfo != NULL) {
4484 msginfo->msgnum = i;
4485 ret = g_slist_append(ret, msginfo);
4499 g_slist_free(sorted_list);
4505 MsgInfo *imap_get_msginfo(Folder *folder, FolderItem *item, gint uid)
4507 MsgInfo *msginfo = NULL;
4508 MsgInfoList *msginfolist;
4509 MsgNumberList numlist;
4511 numlist.next = NULL;
4512 numlist.data = GINT_TO_POINTER(uid);
4514 msginfolist = imap_get_msginfos(folder, item, &numlist);
4515 if (msginfolist != NULL) {
4516 msginfo = msginfolist->data;
4517 g_slist_free(msginfolist);
4523 gboolean imap_scan_required(Folder *folder, FolderItem *_item)
4525 IMAPSession *session;
4526 IMAPFolderItem *item = (IMAPFolderItem *)_item;
4527 gint ok, exists = 0, recent = 0, unseen = 0;
4528 guint32 uid_next, uid_val = 0;
4529 gboolean selected_folder;
4531 g_return_val_if_fail(folder != NULL, FALSE);
4532 g_return_val_if_fail(item != NULL, FALSE);
4533 g_return_val_if_fail(item->item.folder != NULL, FALSE);
4534 g_return_val_if_fail(FOLDER_CLASS(item->item.folder) == &imap_class, FALSE);
4536 if (item->item.path == NULL)
4539 session = imap_session_get(folder);
4540 g_return_val_if_fail(session != NULL, FALSE);
4542 selected_folder = (session->mbox != NULL) &&
4543 (!strcmp(session->mbox, item->item.path));
4544 if (selected_folder) {
4545 ok = imap_cmd_noop(session);
4546 if (ok != IMAP_SUCCESS)
4549 if (session->folder_content_changed)
4552 ok = imap_status(session, IMAP_FOLDER(folder), item->item.path,
4553 &exists, &recent, &uid_next, &uid_val, &unseen, FALSE);
4554 if (ok != IMAP_SUCCESS)
4557 if ((uid_next != item->uid_next) || (exists != item->item.total_msgs))
4564 void imap_change_flags(Folder *folder, FolderItem *item, MsgInfo *msginfo, MsgPermFlags newflags)
4566 IMAPSession *session;
4567 IMAPFlags flags_set = 0, flags_unset = 0;
4568 gint ok = IMAP_SUCCESS;
4569 MsgNumberList numlist;
4570 hashtable_data *ht_data = NULL;
4572 g_return_if_fail(folder != NULL);
4573 g_return_if_fail(folder->klass == &imap_class);
4574 g_return_if_fail(item != NULL);
4575 g_return_if_fail(item->folder == folder);
4576 g_return_if_fail(msginfo != NULL);
4577 g_return_if_fail(msginfo->folder == item);
4579 session = imap_session_get(folder);
4580 if (!session) return;
4582 if ((ok = imap_select(session, IMAP_FOLDER(folder), msginfo->folder->path,
4583 NULL, NULL, NULL, NULL, FALSE)) != IMAP_SUCCESS)
4586 if (!MSG_IS_MARKED(msginfo->flags) && (newflags & MSG_MARKED))
4587 flags_set |= IMAP_FLAG_FLAGGED;
4588 if ( MSG_IS_MARKED(msginfo->flags) && !(newflags & MSG_MARKED))
4589 flags_unset |= IMAP_FLAG_FLAGGED;
4591 if (!MSG_IS_UNREAD(msginfo->flags) && (newflags & MSG_UNREAD))
4592 flags_unset |= IMAP_FLAG_SEEN;
4593 if ( MSG_IS_UNREAD(msginfo->flags) && !(newflags & MSG_UNREAD))
4594 flags_set |= IMAP_FLAG_SEEN;
4596 if (!MSG_IS_REPLIED(msginfo->flags) && (newflags & MSG_REPLIED))
4597 flags_set |= IMAP_FLAG_ANSWERED;
4598 if ( MSG_IS_REPLIED(msginfo->flags) && !(newflags & MSG_REPLIED))
4599 flags_set |= IMAP_FLAG_ANSWERED;
4601 numlist.next = NULL;
4602 numlist.data = GINT_TO_POINTER(msginfo->msgnum);
4604 if (IMAP_FOLDER_ITEM(item)->batching) {
4605 /* instead of performing an UID STORE command for each message change,
4606 * as a lot of them can change "together", we just fill in hashtables
4607 * and defer the treatment so that we're able to send only one
4610 debug_print("IMAP batch mode on, deferring flags change\n");
4612 ht_data = g_hash_table_lookup(flags_set_table, GINT_TO_POINTER(flags_set));
4613 if (ht_data == NULL) {
4614 ht_data = g_new0(hashtable_data, 1);
4615 ht_data->session = session;
4616 g_hash_table_insert(flags_set_table, GINT_TO_POINTER(flags_set), ht_data);
4618 if (!g_slist_find(ht_data->msglist, GINT_TO_POINTER(msginfo->msgnum)))
4619 ht_data->msglist = g_slist_prepend(ht_data->msglist, GINT_TO_POINTER(msginfo->msgnum));
4622 ht_data = g_hash_table_lookup(flags_unset_table, GINT_TO_POINTER(flags_unset));
4623 if (ht_data == NULL) {
4624 ht_data = g_new0(hashtable_data, 1);
4625 ht_data->session = session;
4626 g_hash_table_insert(flags_unset_table, GINT_TO_POINTER(flags_unset), ht_data);
4628 if (!g_slist_find(ht_data->msglist, GINT_TO_POINTER(msginfo->msgnum)))
4629 ht_data->msglist = g_slist_prepend(ht_data->msglist, GINT_TO_POINTER(msginfo->msgnum));
4632 debug_print("IMAP changing flags\n");
4634 ok = imap_set_message_flags(session, &numlist, flags_set, TRUE);
4635 if (ok != IMAP_SUCCESS) return;
4639 ok = imap_set_message_flags(session, &numlist, flags_unset, FALSE);
4640 if (ok != IMAP_SUCCESS) return;
4643 msginfo->flags.perm_flags = newflags;
4648 static gint imap_remove_msg(Folder *folder, FolderItem *item, gint uid)
4651 IMAPSession *session;
4653 MsgNumberList numlist;
4655 g_return_val_if_fail(folder != NULL, -1);
4656 g_return_val_if_fail(FOLDER_CLASS(folder) == &imap_class, -1);
4657 g_return_val_if_fail(item != NULL, -1);
4659 session = imap_session_get(folder);
4660 if (!session) return -1;
4662 ok = imap_select(session, IMAP_FOLDER(folder), item->path,
4663 NULL, NULL, NULL, NULL, FALSE);
4664 if (ok != IMAP_SUCCESS)
4667 numlist.next = NULL;
4668 numlist.data = GINT_TO_POINTER(uid);
4670 ok = imap_set_message_flags
4671 (IMAP_SESSION(REMOTE_FOLDER(folder)->session),
4672 &numlist, IMAP_FLAG_DELETED, TRUE);
4673 if (ok != IMAP_SUCCESS) {
4674 log_warning(_("can't set deleted flags: %d\n"), uid);
4678 if (!session->uidplus) {
4679 ok = imap_cmd_expunge(session, NULL);
4683 uidstr = g_strdup_printf("%u", uid);
4684 ok = imap_cmd_expunge(session, uidstr);
4687 if (ok != IMAP_SUCCESS) {
4688 log_warning(_("can't expunge\n"));
4692 IMAP_FOLDER_ITEM(item)->uid_list = g_slist_remove(
4693 IMAP_FOLDER_ITEM(item)->uid_list, numlist.data);
4694 dir = folder_item_get_path(item);
4695 if (is_dir_exist(dir))
4696 remove_numbered_files(dir, uid, uid);
4699 return IMAP_SUCCESS;
4702 static gint compare_msginfo(gconstpointer a, gconstpointer b)
4704 return ((MsgInfo *)a)->msgnum - ((MsgInfo *)b)->msgnum;
4707 static guint gslist_find_next_num(MsgNumberList **list, guint num)
4711 g_return_val_if_fail(list != NULL, -1);
4713 for (elem = *list; elem != NULL; elem = g_slist_next(elem))
4714 if (GPOINTER_TO_INT(elem->data) >= num)
4717 return elem != NULL ? GPOINTER_TO_INT(elem->data) : (gint)-1;
4721 * NEW and DELETED flags are not syncronized
4722 * - The NEW/RECENT flags in IMAP folders can not really be directly
4723 * modified by Sylpheed
4724 * - The DELETE/DELETED flag in IMAP and Sylpheed don't have the same
4725 * meaning, in IMAP it always removes the messages from the FolderItem
4726 * in Sylpheed it can mean to move the message to trash
4729 typedef struct _get_flags_data {
4732 MsgInfoList *msginfo_list;
4733 GRelation *msgflags;
4737 static /*gint*/ void *imap_get_flags_thread(void *data)
4739 get_flags_data *stuff = (get_flags_data *)data;
4740 Folder *folder = stuff->folder;
4741 FolderItem *item = stuff->item;
4742 MsgInfoList *msginfo_list = stuff->msginfo_list;
4743 GRelation *msgflags = stuff->msgflags;
4744 IMAPSession *session;
4745 GSList *sorted_list;
4746 GSList *unseen = NULL, *answered = NULL, *flagged = NULL;
4747 GSList *p_unseen, *p_answered, *p_flagged;
4749 GSList *seq_list, *cur;
4750 gboolean reverse_seen = FALSE;
4753 gint exists_cnt, recent_cnt, unseen_cnt, uid_next;
4754 guint32 uidvalidity;
4755 gboolean selected_folder;
4757 if (folder == NULL || item == NULL) {
4759 return GINT_TO_POINTER(-1);
4761 if (msginfo_list == NULL) {
4763 return GINT_TO_POINTER(0);
4766 session = imap_session_get(folder);
4767 if (session == NULL) {
4769 return GINT_TO_POINTER(-1);
4772 selected_folder = (session->mbox != NULL) &&
4773 (!strcmp(session->mbox, item->path));
4775 if (!selected_folder) {
4776 ok = imap_status(session, IMAP_FOLDER(folder), item->path,
4777 &exists_cnt, &recent_cnt, &uid_next, &uidvalidity, &unseen_cnt, TRUE);
4778 ok = imap_select(session, IMAP_FOLDER(folder), item->path,
4779 NULL, NULL, NULL, NULL, TRUE);
4780 if (ok != IMAP_SUCCESS) {
4782 return GINT_TO_POINTER(-1);
4787 if (unseen_cnt > exists_cnt / 2)
4788 reverse_seen = TRUE;
4790 cmd_buf = g_string_new(NULL);
4792 sorted_list = g_slist_sort(g_slist_copy(msginfo_list), compare_msginfo);
4794 seq_list = imap_get_seq_set_from_msglist(msginfo_list);
4796 for (cur = seq_list; cur != NULL; cur = g_slist_next(cur)) {
4797 IMAPSet imapset = cur->data;
4799 g_string_sprintf(cmd_buf, "%sSEEN UID %s", reverse_seen ? "" : "UN", imapset);
4800 imap_cmd_search(session, cmd_buf->str, &p_unseen, TRUE);
4801 unseen = g_slist_concat(unseen, p_unseen);
4803 g_string_sprintf(cmd_buf, "ANSWERED UID %s", imapset);
4804 imap_cmd_search(session, cmd_buf->str, &p_answered, TRUE);
4805 answered = g_slist_concat(answered, p_answered);
4807 g_string_sprintf(cmd_buf, "FLAGGED UID %s", imapset);
4808 imap_cmd_search(session, cmd_buf->str, &p_flagged, TRUE);
4809 flagged = g_slist_concat(flagged, p_flagged);
4813 p_answered = answered;
4814 p_flagged = flagged;
4816 for (elem = sorted_list; elem != NULL; elem = g_slist_next(elem)) {
4821 msginfo = (MsgInfo *) elem->data;
4822 flags = msginfo->flags.perm_flags;
4823 wasnew = (flags & MSG_NEW);
4824 flags &= ~((reverse_seen ? 0 : MSG_UNREAD | MSG_NEW) | MSG_REPLIED | MSG_MARKED);
4826 flags |= MSG_UNREAD | (wasnew ? MSG_NEW : 0);
4827 if (gslist_find_next_num(&p_unseen, msginfo->msgnum) == msginfo->msgnum) {
4828 if (!reverse_seen) {
4829 flags |= MSG_UNREAD | (wasnew ? MSG_NEW : 0);
4831 flags &= ~(MSG_UNREAD | MSG_NEW);
4834 if (gslist_find_next_num(&p_answered, msginfo->msgnum) == msginfo->msgnum)
4835 flags |= MSG_REPLIED;
4836 if (gslist_find_next_num(&p_flagged, msginfo->msgnum) == msginfo->msgnum)
4837 flags |= MSG_MARKED;
4838 g_relation_insert(msgflags, msginfo, GINT_TO_POINTER(flags));
4841 imap_seq_set_free(seq_list);
4842 g_slist_free(flagged);
4843 g_slist_free(answered);
4844 g_slist_free(unseen);
4845 g_slist_free(sorted_list);
4846 g_string_free(cmd_buf, TRUE);
4849 return GINT_TO_POINTER(0);
4852 static gint imap_get_flags(Folder *folder, FolderItem *item,
4853 MsgInfoList *msginfo_list, GRelation *msgflags)
4856 get_flags_data *data = g_new0(get_flags_data, 1);
4862 data->folder = folder;
4864 data->msginfo_list = msginfo_list;
4865 data->msgflags = msgflags;
4867 if (prefs_common.work_offline && !imap_gtk_should_override()) {
4872 #if (defined USE_PTHREAD && defined __GLIBC__ && (__GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 3)))
4873 if (pthread_create(&pt, PTHREAD_CREATE_JOINABLE,
4874 imap_get_flags_thread, data) != 0) {
4875 result = GPOINTER_TO_INT(imap_get_flags_thread(data));
4879 debug_print("+++waiting for imap_get_flags_thread...\n");
4880 while(!data->done) {
4881 /* don't let the interface freeze while waiting */
4884 debug_print("---imap_get_flags_thread done\n");
4886 /* get the thread's return value and clean its resources */
4887 pthread_join(pt, &tmp);
4888 result = GPOINTER_TO_INT(tmp);
4890 result = GPOINTER_TO_INT(imap_get_flags_thread(data));
4897 static gboolean process_flags(gpointer key, gpointer value, gpointer user_data)
4899 gboolean flags_set = GPOINTER_TO_INT(user_data);
4900 gint flags_value = GPOINTER_TO_INT(key);
4901 hashtable_data *data = (hashtable_data *)value;
4903 data->msglist = g_slist_reverse(data->msglist);
4905 debug_print("IMAP %ssetting flags to %d for %d messages\n",
4908 g_slist_length(data->msglist));
4909 imap_set_message_flags(data->session, data->msglist, flags_value, flags_set);
4911 g_slist_free(data->msglist);
4916 static void process_hashtable(void)
4918 if (flags_set_table) {
4919 g_hash_table_foreach_remove(flags_set_table, process_flags, GINT_TO_POINTER(TRUE));
4920 g_free(flags_set_table);
4921 flags_set_table = NULL;
4923 if (flags_unset_table) {
4924 g_hash_table_foreach_remove(flags_unset_table, process_flags, GINT_TO_POINTER(FALSE));
4925 g_free(flags_unset_table);
4926 flags_unset_table = NULL;
4930 static IMAPFolderItem *batching_item = NULL;
4932 static void imap_set_batch (Folder *folder, FolderItem *_item, gboolean batch)
4934 IMAPFolderItem *item = (IMAPFolderItem *)_item;
4936 g_return_if_fail(item != NULL);
4938 if (batch && batching_item != NULL) {
4939 g_warning("already batching on %s\n", batching_item->item.path);
4943 if (item->batching == batch)
4946 item->batching = batch;
4948 batching_item = batch?item:NULL;
4951 debug_print("IMAP switching to batch mode\n");
4952 if (flags_set_table) {
4953 g_warning("flags_set_table non-null but we just entered batch mode!\n");
4954 flags_set_table = NULL;
4956 if (flags_unset_table) {
4957 g_warning("flags_unset_table non-null but we just entered batch mode!\n");
4958 flags_unset_table = NULL;
4960 flags_set_table = g_hash_table_new(NULL, g_direct_equal);
4961 flags_unset_table = g_hash_table_new(NULL, g_direct_equal);
4963 debug_print("IMAP switching away from batch mode\n");
4965 process_hashtable();