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"
68 typedef struct _thread_data {
80 typedef struct _IMAPFolder IMAPFolder;
81 typedef struct _IMAPSession IMAPSession;
82 typedef struct _IMAPNameSpace IMAPNameSpace;
83 typedef struct _IMAPFolderItem IMAPFolderItem;
85 #include "prefs_account.h"
87 #define IMAP_FOLDER(obj) ((IMAPFolder *)obj)
88 #define IMAP_FOLDER_ITEM(obj) ((IMAPFolderItem *)obj)
89 #define IMAP_SESSION(obj) ((IMAPSession *)obj)
95 /* list of IMAPNameSpace */
105 gboolean authenticated;
114 gboolean folder_content_changed;
118 struct _IMAPNameSpace
124 #define IMAP_SUCCESS 0
125 #define IMAP_SOCKET 2
126 #define IMAP_AUTHFAIL 3
127 #define IMAP_PROTOCOL 4
128 #define IMAP_SYNTAX 5
132 #define IMAPBUFSIZE 8192
136 IMAP_FLAG_SEEN = 1 << 0,
137 IMAP_FLAG_ANSWERED = 1 << 1,
138 IMAP_FLAG_FLAGGED = 1 << 2,
139 IMAP_FLAG_DELETED = 1 << 3,
140 IMAP_FLAG_DRAFT = 1 << 4
143 #define IMAP_IS_SEEN(flags) ((flags & IMAP_FLAG_SEEN) != 0)
144 #define IMAP_IS_ANSWERED(flags) ((flags & IMAP_FLAG_ANSWERED) != 0)
145 #define IMAP_IS_FLAGGED(flags) ((flags & IMAP_FLAG_FLAGGED) != 0)
146 #define IMAP_IS_DELETED(flags) ((flags & IMAP_FLAG_DELETED) != 0)
147 #define IMAP_IS_DRAFT(flags) ((flags & IMAP_FLAG_DRAFT) != 0)
150 #define IMAP4_PORT 143
152 #define IMAPS_PORT 993
155 #define IMAP_CMD_LIMIT 1000
157 #define QUOTE_IF_REQUIRED(out, str) \
159 if (*str != '"' && strpbrk(str, " \t(){}[]%*") != NULL) { \
163 len = strlen(str) + 3; \
164 Xalloca(__tmp, len, return IMAP_ERROR); \
165 g_snprintf(__tmp, len, "\"%s\"", str); \
168 Xstrdup_a(out, str, return IMAP_ERROR); \
172 typedef gchar * IMAPSet;
174 struct _IMAPFolderItem
183 static void imap_folder_init (Folder *folder,
187 static Folder *imap_folder_new (const gchar *name,
189 static void imap_folder_destroy (Folder *folder);
191 static IMAPSession *imap_session_new (const PrefsAccount *account);
192 static void imap_session_authenticate(IMAPSession *session,
193 const PrefsAccount *account);
194 static void imap_session_destroy (Session *session);
196 static gchar *imap_fetch_msg (Folder *folder,
199 static gint imap_add_msg (Folder *folder,
203 static gint imap_add_msgs (Folder *folder,
206 GRelation *relation);
208 static gint imap_copy_msg (Folder *folder,
211 static gint imap_copy_msgs (Folder *folder,
213 MsgInfoList *msglist,
214 GRelation *relation);
216 static gint imap_remove_msg (Folder *folder,
219 static gint imap_remove_all_msg (Folder *folder,
222 static gboolean imap_is_msg_changed (Folder *folder,
226 static gint imap_close (Folder *folder,
229 static gint imap_scan_tree (Folder *folder);
231 static gint imap_create_tree (Folder *folder);
233 static FolderItem *imap_create_folder (Folder *folder,
236 static gint imap_rename_folder (Folder *folder,
239 static gint imap_remove_folder (Folder *folder,
242 static FolderItem *imap_folder_item_new (Folder *folder);
243 static void imap_folder_item_destroy (Folder *folder,
246 static IMAPSession *imap_session_get (Folder *folder);
248 static gint imap_greeting (IMAPSession *session);
249 static gint imap_auth (IMAPSession *session,
254 static gint imap_scan_tree_recursive (IMAPSession *session,
256 static GSList *imap_parse_list (IMAPFolder *folder,
257 IMAPSession *session,
258 const gchar *real_path,
261 static void imap_create_missing_folders (Folder *folder);
262 static FolderItem *imap_create_special_folder
264 SpecialFolderItemType stype,
267 static gint imap_do_copy_msgs (Folder *folder,
269 MsgInfoList *msglist,
270 GRelation *relation);
272 static void imap_delete_all_cached_messages (FolderItem *item);
275 static SockInfo *imap_open (const gchar *server,
279 static SockInfo *imap_open (const gchar *server,
284 static SockInfo *imap_open_tunnel(const gchar *server,
285 const gchar *tunnelcmd,
288 static SockInfo *imap_open_tunnel(const gchar *server,
289 const gchar *tunnelcmd);
293 static SockInfo *imap_init_sock(SockInfo *sock, SSLType ssl_type);
295 static SockInfo *imap_init_sock(SockInfo *sock);
298 static gchar *imap_get_flag_str (IMAPFlags flags);
299 static gint imap_set_message_flags (IMAPSession *session,
300 MsgNumberList *numlist,
303 static gint imap_select (IMAPSession *session,
309 guint32 *uid_validity);
310 static gint imap_status (IMAPSession *session,
316 guint32 *uid_validity,
319 static void imap_parse_namespace (IMAPSession *session,
321 static void imap_get_namespace_by_list (IMAPSession *session,
323 static IMAPNameSpace *imap_find_namespace (IMAPFolder *folder,
325 static gchar imap_get_path_separator (IMAPFolder *folder,
327 static gchar *imap_get_real_path (IMAPFolder *folder,
330 static gchar *imap_parse_atom (SockInfo *sock,
335 static MsgFlags imap_parse_flags (const gchar *flag_str);
336 static MsgInfo *imap_parse_envelope (SockInfo *sock,
340 static gboolean imap_has_capability (IMAPSession *session,
342 static void imap_free_capabilities (IMAPSession *session);
344 /* low-level IMAP4rev1 commands */
345 static gint imap_cmd_authenticate
346 (IMAPSession *session,
350 static gint imap_cmd_login (IMAPSession *session,
353 static gint imap_cmd_logout (IMAPSession *session);
354 static gint imap_cmd_noop (IMAPSession *session);
356 static gint imap_cmd_starttls (IMAPSession *session);
358 static gint imap_cmd_namespace (IMAPSession *session,
360 static gint imap_cmd_list (IMAPSession *session,
362 const gchar *mailbox,
364 static gint imap_cmd_do_select (IMAPSession *session,
370 guint32 *uid_validity);
371 static gint imap_cmd_select (IMAPSession *session,
376 guint32 *uid_validity);
377 static gint imap_cmd_examine (IMAPSession *session,
382 guint32 *uid_validity);
383 static gint imap_cmd_create (IMAPSession *sock,
384 const gchar *folder);
385 static gint imap_cmd_rename (IMAPSession *sock,
386 const gchar *oldfolder,
387 const gchar *newfolder);
388 static gint imap_cmd_delete (IMAPSession *session,
389 const gchar *folder);
390 static gint imap_cmd_envelope (IMAPSession *session,
392 static gint imap_cmd_fetch (IMAPSession *sock,
394 const gchar *filename);
395 static gint imap_cmd_append (IMAPSession *session,
396 const gchar *destfolder,
400 static gint imap_cmd_copy (IMAPSession *session,
401 const gchar *seq_set,
402 const gchar *destfolder,
403 GRelation *uid_mapping);
404 static gint imap_cmd_store (IMAPSession *session,
407 static gint imap_cmd_expunge (IMAPSession *session,
409 static gint imap_cmd_close (IMAPSession *session);
411 static gint imap_cmd_ok (IMAPSession *session,
413 static gint imap_cmd_ok_block (IMAPSession *session,
415 static void imap_gen_send (IMAPSession *session,
416 const gchar *format, ...);
417 static gint imap_gen_recv (IMAPSession *session,
419 static gint imap_gen_recv_block (IMAPSession *session,
421 static gint imap_gen_recv_with_block
422 (IMAPSession *session,
426 /* misc utility functions */
427 static gchar *strchr_cpy (const gchar *src,
431 static gchar *get_quoted (const gchar *src,
435 static gchar *search_array_contain_str (GPtrArray *array,
437 static gchar *search_array_str (GPtrArray *array,
439 static void imap_path_separator_subst (gchar *str,
442 static gchar *imap_utf8_to_modified_utf7 (const gchar *from);
443 static gchar *imap_modified_utf7_to_utf8 (const gchar *mutf7_str);
445 static GSList *imap_get_seq_set_from_numlist (MsgNumberList *msglist);
446 static GSList *imap_get_seq_set_from_msglist (MsgInfoList *msglist);
447 static void imap_seq_set_free (GSList *seq_list);
449 static gboolean imap_rename_folder_func (GNode *node,
451 static gint imap_get_num_list (Folder *folder,
454 gboolean *old_uids_valid);
455 static GSList *imap_get_msginfos (Folder *folder,
457 GSList *msgnum_list);
458 static MsgInfo *imap_get_msginfo (Folder *folder,
461 static gboolean imap_scan_required (Folder *folder,
463 static void imap_change_flags (Folder *folder,
466 MsgPermFlags newflags);
467 static gint imap_get_flags (Folder *folder,
469 MsgInfoList *msglist,
470 GRelation *msgflags);
471 static gchar *imap_folder_get_path (Folder *folder);
472 static gchar *imap_item_get_path (Folder *folder,
475 static FolderClass imap_class;
478 void *imap_getline_thread(void *data)
480 thread_data *td = (thread_data *)data;
483 line = sock_getline(td->sock);
491 static gchar *imap_getline(SockInfo *sock)
493 #if (defined USE_PTHREAD && defined __GLIBC__ && (__GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 3)))
494 thread_data *td = g_new0(thread_data, 1);
500 debug_print("creating imap_getline_thread...\n");
501 if (pthread_create(&pt, PTHREAD_CREATE_JOINABLE,
502 imap_getline_thread, td) != 0) {
504 return sock_getline(sock);
507 debug_print("waiting for imap_getline_thread...\n");
509 /* don't let the interface freeze while waiting */
513 /* get the thread's return value and clean its resources */
514 pthread_join(pt, (void *)&line);
517 debug_print("imap_getline_thread returned %s\n", line);
520 return sock_getline(sock);
524 FolderClass *imap_get_class(void)
526 if (imap_class.idstr == NULL) {
527 imap_class.type = F_IMAP;
528 imap_class.idstr = "imap";
529 imap_class.uistr = "IMAP4";
531 /* Folder functions */
532 imap_class.new_folder = imap_folder_new;
533 imap_class.destroy_folder = imap_folder_destroy;
534 imap_class.scan_tree = imap_scan_tree;
535 imap_class.create_tree = imap_create_tree;
537 /* FolderItem functions */
538 imap_class.item_new = imap_folder_item_new;
539 imap_class.item_destroy = imap_folder_item_destroy;
540 imap_class.item_get_path = imap_item_get_path;
541 imap_class.create_folder = imap_create_folder;
542 imap_class.rename_folder = imap_rename_folder;
543 imap_class.remove_folder = imap_remove_folder;
544 imap_class.close = imap_close;
545 imap_class.get_num_list = imap_get_num_list;
546 imap_class.scan_required = imap_scan_required;
548 /* Message functions */
549 imap_class.get_msginfo = imap_get_msginfo;
550 imap_class.get_msginfos = imap_get_msginfos;
551 imap_class.fetch_msg = imap_fetch_msg;
552 imap_class.add_msg = imap_add_msg;
553 imap_class.add_msgs = imap_add_msgs;
554 imap_class.copy_msg = imap_copy_msg;
555 imap_class.copy_msgs = imap_copy_msgs;
556 imap_class.remove_msg = imap_remove_msg;
557 imap_class.remove_all_msg = imap_remove_all_msg;
558 imap_class.is_msg_changed = imap_is_msg_changed;
559 imap_class.change_flags = imap_change_flags;
560 imap_class.get_flags = imap_get_flags;
566 static Folder *imap_folder_new(const gchar *name, const gchar *path)
570 folder = (Folder *)g_new0(IMAPFolder, 1);
571 folder->klass = &imap_class;
572 imap_folder_init(folder, name, path);
577 static void imap_folder_destroy(Folder *folder)
581 dir = imap_folder_get_path(folder);
582 if (is_dir_exist(dir))
583 remove_dir_recursive(dir);
586 folder_remote_folder_destroy(REMOTE_FOLDER(folder));
589 static void imap_folder_init(Folder *folder, const gchar *name,
592 folder_remote_folder_init((Folder *)folder, name, path);
595 static FolderItem *imap_folder_item_new(Folder *folder)
597 IMAPFolderItem *item;
599 item = g_new0(IMAPFolderItem, 1);
602 item->uid_list = NULL;
604 return (FolderItem *)item;
607 static void imap_folder_item_destroy(Folder *folder, FolderItem *_item)
609 IMAPFolderItem *item = (IMAPFolderItem *)_item;
611 g_return_if_fail(item != NULL);
612 g_slist_free(item->uid_list);
617 static gboolean imap_reset_uid_lists_func(GNode *node, gpointer data)
619 IMAPFolderItem *item = (IMAPFolderItem *)node->data;
623 g_slist_free(item->uid_list);
624 item->uid_list = NULL;
629 static void imap_reset_uid_lists(Folder *folder)
631 if(folder->node == NULL)
634 /* Destroy all uid lists and rest last uid */
635 g_node_traverse(folder->node, G_IN_ORDER, G_TRAVERSE_ALL, -1, imap_reset_uid_lists_func, NULL);
638 /* Send CAPABILITY, and examine the server's response to see whether this
639 * connection is pre-authenticated or not and build a list of CAPABILITIES. */
640 static gint imap_greeting(IMAPSession *session)
645 imap_gen_send(session, "CAPABILITY");
647 argbuf = g_ptr_array_new();
649 if (imap_cmd_ok(session, argbuf) != IMAP_SUCCESS ||
650 ((capstr = search_array_str(argbuf, "CAPABILITY ")) == NULL)) {
651 ptr_array_free_strings(argbuf);
652 g_ptr_array_free(argbuf, TRUE);
656 session->authenticated = search_array_str(argbuf, "PREAUTH") != NULL;
658 capstr += strlen("CAPABILITY ");
660 IMAP_SESSION(session)->capability = g_strsplit(capstr, " ", 0);
662 ptr_array_free_strings(argbuf);
663 g_ptr_array_free(argbuf, TRUE);
665 if (imap_has_capability(session, "UIDPLUS"))
666 session->uidplus = TRUE;
671 static gint imap_auth(IMAPSession *session, const gchar *user, const gchar *pass,
676 if (type == 0 || type == IMAP_AUTH_LOGIN)
677 ok = imap_cmd_login(session, user, pass);
679 ok = imap_cmd_authenticate(session, user, pass, type);
681 if (ok == IMAP_SUCCESS)
682 session->authenticated = TRUE;
687 static IMAPSession *imap_session_get(Folder *folder)
689 RemoteFolder *rfolder = REMOTE_FOLDER(folder);
690 IMAPSession *session = NULL;
692 g_return_val_if_fail(folder != NULL, NULL);
693 g_return_val_if_fail(FOLDER_CLASS(folder) == &imap_class, NULL);
694 g_return_val_if_fail(folder->account != NULL, NULL);
696 if (prefs_common.work_offline && !imap_gtk_should_override())
699 /* Make sure we have a session */
700 if (rfolder->session != NULL) {
701 session = IMAP_SESSION(rfolder->session);
703 imap_reset_uid_lists(folder);
704 session = imap_session_new(folder->account);
709 if (SESSION(session)->sock->state == CONN_DISCONNECTED) {
710 debug_print("IMAP server disconnected\n");
711 session_destroy(SESSION(session));
712 imap_reset_uid_lists(folder);
713 session = imap_session_new(folder->account);
716 /* Make sure session is authenticated */
717 if (!IMAP_SESSION(session)->authenticated)
718 imap_session_authenticate(IMAP_SESSION(session), folder->account);
719 if (!IMAP_SESSION(session)->authenticated) {
720 session_destroy(SESSION(session));
721 rfolder->session = NULL;
725 /* Make sure we have parsed the IMAP namespace */
726 imap_parse_namespace(IMAP_SESSION(session),
727 IMAP_FOLDER(folder));
729 /* I think the point of this code is to avoid sending a
730 * keepalive if we've used the session recently and therefore
731 * think it's still alive. Unfortunately, most of the code
732 * does not yet check for errors on the socket, and so if the
733 * connection drops we don't notice until the timeout expires.
734 * A better solution than sending a NOOP every time would be
735 * for every command to be prepared to retry until it is
736 * successfully sent. -- mbp */
737 if (time(NULL) - SESSION(session)->last_access_time > SESSION_TIMEOUT_INTERVAL) {
738 /* verify that the session is still alive */
739 if (imap_cmd_noop(session) != IMAP_SUCCESS) {
740 /* Check if this is the first try to establish a
741 connection, if yes we don't try to reconnect */
742 if (rfolder->session == NULL) {
743 log_warning(_("Connecting to %s failed"),
744 folder->account->recv_server);
745 session_destroy(SESSION(session));
748 log_warning(_("IMAP4 connection to %s has been"
749 " disconnected. Reconnecting...\n"),
750 folder->account->recv_server);
751 statusbar_print_all(_("IMAP4 connection to %s has been"
752 " disconnected. Reconnecting...\n"),
753 folder->account->recv_server);
754 session_destroy(SESSION(session));
755 /* Clear folders session to make imap_session_get create
756 a new session, because of rfolder->session == NULL
757 it will not try to reconnect again and so avoid an
759 rfolder->session = NULL;
760 session = imap_session_get(folder);
766 rfolder->session = SESSION(session);
768 return IMAP_SESSION(session);
771 static IMAPSession *imap_session_new(const PrefsAccount *account)
773 IMAPSession *session;
778 /* FIXME: IMAP over SSL only... */
781 port = account->set_imapport ? account->imapport
782 : account->ssl_imap == SSL_TUNNEL ? IMAPS_PORT : IMAP4_PORT;
783 ssl_type = account->ssl_imap;
785 port = account->set_imapport ? account->imapport
789 if (account->set_tunnelcmd) {
790 log_message(_("creating tunneled IMAP4 connection\n"));
792 if ((imap_sock = imap_open_tunnel(account->recv_server,
796 if ((imap_sock = imap_open_tunnel(account->recv_server,
797 account->tunnelcmd)) == NULL)
801 g_return_val_if_fail(account->recv_server != NULL, NULL);
803 log_message(_("creating IMAP4 connection to %s:%d ...\n"),
804 account->recv_server, port);
807 if ((imap_sock = imap_open(account->recv_server, port,
810 if ((imap_sock = imap_open(account->recv_server, port)) == NULL)
815 session = g_new0(IMAPSession, 1);
816 session_init(SESSION(session));
817 SESSION(session)->type = SESSION_IMAP;
818 SESSION(session)->server = g_strdup(account->recv_server);
819 SESSION(session)->sock = imap_sock;
821 SESSION(session)->destroy = imap_session_destroy;
823 session->capability = NULL;
825 session->authenticated = FALSE;
826 session->mbox = NULL;
827 session->cmd_count = 0;
829 /* Only need to log in if the connection was not PREAUTH */
830 if (imap_greeting(session) != IMAP_SUCCESS) {
831 session_destroy(SESSION(session));
836 if (account->ssl_imap == SSL_STARTTLS &&
837 imap_has_capability(session, "STARTTLS")) {
840 ok = imap_cmd_starttls(session);
841 if (ok != IMAP_SUCCESS) {
842 log_warning(_("Can't start TLS session.\n"));
843 session_destroy(SESSION(session));
846 if (!ssl_init_socket_with_method(SESSION(session)->sock,
848 session_destroy(SESSION(session));
852 imap_free_capabilities(session);
853 session->authenticated = FALSE;
854 session->uidplus = FALSE;
855 session->cmd_count = 1;
857 if (imap_greeting(session) != IMAP_SUCCESS) {
858 session_destroy(SESSION(session));
863 log_message("IMAP connection is %s-authenticated\n",
864 (session->authenticated) ? "pre" : "un");
869 static void imap_session_authenticate(IMAPSession *session,
870 const PrefsAccount *account)
874 g_return_if_fail(account->userid != NULL);
876 pass = account->passwd;
879 tmp_pass = input_dialog_query_password(account->recv_server, account->userid);
882 Xstrdup_a(pass, tmp_pass, {g_free(tmp_pass); return;});
886 if (imap_auth(session, account->userid, pass, account->imap_auth_type) != IMAP_SUCCESS) {
887 imap_cmd_logout(session);
891 session->authenticated = TRUE;
894 static void imap_session_destroy(Session *session)
896 imap_free_capabilities(IMAP_SESSION(session));
897 g_free(IMAP_SESSION(session)->mbox);
898 sock_close(session->sock);
899 session->sock = NULL;
902 static gchar *imap_fetch_msg(Folder *folder, FolderItem *item, gint uid)
904 gchar *path, *filename;
905 IMAPSession *session;
908 g_return_val_if_fail(folder != NULL, NULL);
909 g_return_val_if_fail(item != NULL, NULL);
911 path = folder_item_get_path(item);
912 if (!is_dir_exist(path))
914 filename = g_strconcat(path, G_DIR_SEPARATOR_S, itos(uid), NULL);
917 if (is_file_exist(filename)) {
918 debug_print("message %d has been already cached.\n", uid);
922 session = imap_session_get(folder);
928 ok = imap_select(session, IMAP_FOLDER(folder), item->path,
929 NULL, NULL, NULL, NULL);
930 if (ok != IMAP_SUCCESS) {
931 g_warning("can't select mailbox %s\n", item->path);
936 debug_print("getting message %d...\n", uid);
937 ok = imap_cmd_fetch(session, (guint32)uid, filename);
939 if (ok != IMAP_SUCCESS) {
940 g_warning("can't fetch message %d\n", uid);
948 static gint imap_add_msg(Folder *folder, FolderItem *dest,
949 const gchar *file, MsgFlags *flags)
953 MsgFileInfo fileinfo;
955 g_return_val_if_fail(file != NULL, -1);
957 fileinfo.msginfo = NULL;
958 fileinfo.file = (gchar *)file;
959 fileinfo.flags = flags;
960 file_list.data = &fileinfo;
961 file_list.next = NULL;
963 ret = imap_add_msgs(folder, dest, &file_list, NULL);
967 static gint imap_add_msgs(Folder *folder, FolderItem *dest, GSList *file_list,
971 IMAPSession *session;
972 guint32 last_uid = 0;
974 MsgFileInfo *fileinfo;
977 g_return_val_if_fail(folder != NULL, -1);
978 g_return_val_if_fail(dest != NULL, -1);
979 g_return_val_if_fail(file_list != NULL, -1);
981 session = imap_session_get(folder);
982 if (!session) return -1;
984 destdir = imap_get_real_path(IMAP_FOLDER(folder), dest->path);
986 for (cur = file_list; cur != NULL; cur = cur->next) {
987 IMAPFlags iflags = 0;
990 fileinfo = (MsgFileInfo *)cur->data;
992 if (fileinfo->flags) {
993 if (MSG_IS_MARKED(*fileinfo->flags))
994 iflags |= IMAP_FLAG_FLAGGED;
995 if (MSG_IS_REPLIED(*fileinfo->flags))
996 iflags |= IMAP_FLAG_ANSWERED;
997 if (!MSG_IS_UNREAD(*fileinfo->flags))
998 iflags |= IMAP_FLAG_SEEN;
1001 if (dest->stype == F_OUTBOX ||
1002 dest->stype == F_QUEUE ||
1003 dest->stype == F_DRAFT ||
1004 dest->stype == F_TRASH)
1005 iflags |= IMAP_FLAG_SEEN;
1007 ok = imap_cmd_append(session, destdir, fileinfo->file, iflags,
1010 if (ok != IMAP_SUCCESS) {
1011 g_warning("can't append message %s\n", fileinfo->file);
1016 if (relation != NULL)
1017 g_relation_insert(relation, fileinfo->msginfo != NULL ?
1018 (gpointer) fileinfo->msginfo : (gpointer) fileinfo,
1019 GINT_TO_POINTER(dest->last_num + 1));
1020 if (last_uid < new_uid)
1029 static gint imap_do_copy_msgs(Folder *folder, FolderItem *dest,
1030 MsgInfoList *msglist, GRelation *relation)
1034 GSList *seq_list, *cur;
1036 IMAPSession *session;
1037 gint ok = IMAP_SUCCESS;
1038 GRelation *uid_mapping;
1041 g_return_val_if_fail(folder != NULL, -1);
1042 g_return_val_if_fail(dest != NULL, -1);
1043 g_return_val_if_fail(msglist != NULL, -1);
1045 session = imap_session_get(folder);
1046 if (!session) return -1;
1048 msginfo = (MsgInfo *)msglist->data;
1050 src = msginfo->folder;
1052 g_warning("the src folder is identical to the dest.\n");
1056 ok = imap_select(session, IMAP_FOLDER(folder), msginfo->folder->path,
1057 NULL, NULL, NULL, NULL);
1058 if (ok != IMAP_SUCCESS)
1061 destdir = imap_get_real_path(IMAP_FOLDER(folder), dest->path);
1062 seq_list = imap_get_seq_set_from_msglist(msglist);
1063 uid_mapping = g_relation_new(2);
1064 g_relation_index(uid_mapping, 0, g_direct_hash, g_direct_equal);
1066 for (cur = seq_list; cur != NULL; cur = g_slist_next(cur)) {
1067 gchar *seq_set = (gchar *)cur->data;
1069 debug_print("Copying message %s%c[%s] to %s ...\n",
1070 src->path, G_DIR_SEPARATOR,
1073 ok = imap_cmd_copy(session, seq_set, destdir, uid_mapping);
1074 if (ok != IMAP_SUCCESS) {
1075 g_relation_destroy(uid_mapping);
1076 imap_seq_set_free(seq_list);
1081 for (cur = msglist; cur != NULL; cur = g_slist_next(cur)) {
1082 MsgInfo *msginfo = (MsgInfo *)cur->data;
1085 tuples = g_relation_select(uid_mapping,
1086 GINT_TO_POINTER(msginfo->msgnum),
1088 if (tuples->len > 0) {
1089 gint num = GPOINTER_TO_INT(g_tuples_index(tuples, 0, 1));
1090 g_relation_insert(relation, msginfo,
1091 GPOINTER_TO_INT(num));
1095 g_relation_insert(relation, msginfo,
1096 GPOINTER_TO_INT(0));
1097 g_tuples_destroy(tuples);
1100 g_relation_destroy(uid_mapping);
1101 imap_seq_set_free(seq_list);
1105 if (ok == IMAP_SUCCESS)
1111 static gint imap_copy_msg(Folder *folder, FolderItem *dest, MsgInfo *msginfo)
1115 g_return_val_if_fail(msginfo != NULL, -1);
1117 msglist.data = msginfo;
1118 msglist.next = NULL;
1120 return imap_copy_msgs(folder, dest, &msglist, NULL);
1123 static gint imap_copy_msgs(Folder *folder, FolderItem *dest,
1124 MsgInfoList *msglist, GRelation *relation)
1130 g_return_val_if_fail(folder != NULL, -1);
1131 g_return_val_if_fail(dest != NULL, -1);
1132 g_return_val_if_fail(msglist != NULL, -1);
1134 msginfo = (MsgInfo *)msglist->data;
1135 g_return_val_if_fail(msginfo->folder != NULL, -1);
1137 if (folder == msginfo->folder->folder)
1138 return imap_do_copy_msgs(folder, dest, msglist, relation);
1140 file_list = procmsg_get_message_file_list(msglist);
1141 g_return_val_if_fail(file_list != NULL, -1);
1143 ret = imap_add_msgs(folder, dest, file_list, relation);
1145 procmsg_message_file_list_free(file_list);
1150 static gint imap_remove_msg(Folder *folder, FolderItem *item, gint uid)
1153 IMAPSession *session;
1155 MsgNumberList numlist;
1157 g_return_val_if_fail(folder != NULL, -1);
1158 g_return_val_if_fail(FOLDER_CLASS(folder) == &imap_class, -1);
1159 g_return_val_if_fail(item != NULL, -1);
1161 session = imap_session_get(folder);
1162 if (!session) return -1;
1164 ok = imap_select(session, IMAP_FOLDER(folder), item->path,
1165 NULL, NULL, NULL, NULL);
1166 if (ok != IMAP_SUCCESS)
1169 numlist.next = NULL;
1170 numlist.data = GINT_TO_POINTER(uid);
1172 ok = imap_set_message_flags
1173 (IMAP_SESSION(REMOTE_FOLDER(folder)->session),
1174 &numlist, IMAP_FLAG_DELETED, TRUE);
1175 if (ok != IMAP_SUCCESS) {
1176 log_warning(_("can't set deleted flags: %d\n"), uid);
1180 if (!session->uidplus) {
1181 ok = imap_cmd_expunge(session, NULL);
1185 uidstr = g_strdup_printf("%u", uid);
1186 ok = imap_cmd_expunge(session, uidstr);
1189 if (ok != IMAP_SUCCESS) {
1190 log_warning(_("can't expunge\n"));
1194 IMAP_FOLDER_ITEM(item)->uid_list = g_slist_remove(
1195 IMAP_FOLDER_ITEM(item)->uid_list, numlist.data);
1196 dir = folder_item_get_path(item);
1197 if (is_dir_exist(dir))
1198 remove_numbered_files(dir, uid, uid);
1201 return IMAP_SUCCESS;
1204 static gint imap_remove_all_msg(Folder *folder, FolderItem *item)
1207 IMAPSession *session;
1210 g_return_val_if_fail(folder != NULL, -1);
1211 g_return_val_if_fail(item != NULL, -1);
1213 session = imap_session_get(folder);
1214 if (!session) return -1;
1216 ok = imap_select(session, IMAP_FOLDER(folder), item->path,
1217 NULL, NULL, NULL, NULL);
1218 if (ok != IMAP_SUCCESS)
1221 imap_gen_send(session, "STORE 1:* +FLAGS.SILENT (\\Deleted)");
1222 ok = imap_cmd_ok(session, NULL);
1223 if (ok != IMAP_SUCCESS) {
1224 log_warning(_("can't set deleted flags: 1:*\n"));
1228 ok = imap_cmd_expunge(session, NULL);
1229 if (ok != IMAP_SUCCESS) {
1230 log_warning(_("can't expunge\n"));
1234 dir = folder_item_get_path(item);
1235 if (is_dir_exist(dir))
1236 remove_all_numbered_files(dir);
1239 return IMAP_SUCCESS;
1242 static gboolean imap_is_msg_changed(Folder *folder, FolderItem *item,
1245 /* TODO: properly implement this method */
1249 static gint imap_close(Folder *folder, FolderItem *item)
1252 IMAPSession *session;
1254 g_return_val_if_fail(folder != NULL, -1);
1255 g_return_val_if_fail(item != NULL, -1);
1256 g_return_val_if_fail(item->path != NULL, -1);
1258 session = imap_session_get(folder);
1259 if (!session) return -1;
1261 if (session->mbox) {
1262 if (strcmp2(session->mbox, item->path) != 0) return -1;
1264 ok = imap_cmd_close(session);
1265 if (ok != IMAP_SUCCESS)
1266 log_warning(_("can't close folder\n"));
1268 g_free(session->mbox);
1269 session->mbox = NULL;
1277 static gint imap_scan_tree(Folder *folder)
1279 FolderItem *item = NULL;
1280 IMAPSession *session;
1281 gchar *root_folder = NULL;
1283 g_return_val_if_fail(folder != NULL, -1);
1284 g_return_val_if_fail(folder->account != NULL, -1);
1286 session = imap_session_get(folder);
1288 if (!folder->node) {
1289 folder_tree_destroy(folder);
1290 item = folder_item_new(folder, folder->name, NULL);
1291 item->folder = folder;
1292 folder->node = item->node = g_node_new(item);
1297 if (folder->account->imap_dir && *folder->account->imap_dir) {
1302 Xstrdup_a(root_folder, folder->account->imap_dir, return -1);
1303 extract_quote(root_folder, '"');
1304 subst_char(root_folder,
1305 imap_get_path_separator(IMAP_FOLDER(folder),
1308 strtailchomp(root_folder, '/');
1309 real_path = imap_get_real_path
1310 (IMAP_FOLDER(folder), root_folder);
1311 debug_print("IMAP root directory: %s\n", real_path);
1313 /* check if root directory exist */
1314 argbuf = g_ptr_array_new();
1315 ok = imap_cmd_list(session, NULL, real_path, argbuf);
1316 if (ok != IMAP_SUCCESS ||
1317 search_array_str(argbuf, "LIST ") == NULL) {
1318 log_warning(_("root folder %s does not exist\n"), real_path);
1319 g_ptr_array_free(argbuf, TRUE);
1322 if (!folder->node) {
1323 item = folder_item_new(folder, folder->name, NULL);
1324 item->folder = folder;
1325 folder->node = item->node = g_node_new(item);
1329 g_ptr_array_free(argbuf, TRUE);
1334 item = FOLDER_ITEM(folder->node->data);
1335 if (!item || ((item->path || root_folder) &&
1336 strcmp2(item->path, root_folder) != 0)) {
1337 folder_tree_destroy(folder);
1338 item = folder_item_new(folder, folder->name, root_folder);
1339 item->folder = folder;
1340 folder->node = item->node = g_node_new(item);
1343 imap_scan_tree_recursive(session, FOLDER_ITEM(folder->node->data));
1344 imap_create_missing_folders(folder);
1349 static gint imap_scan_tree_recursive(IMAPSession *session, FolderItem *item)
1352 IMAPFolder *imapfolder;
1353 FolderItem *new_item;
1354 GSList *item_list, *cur;
1357 gchar *wildcard_path;
1361 g_return_val_if_fail(item != NULL, -1);
1362 g_return_val_if_fail(item->folder != NULL, -1);
1363 g_return_val_if_fail(item->no_sub == FALSE, -1);
1365 folder = item->folder;
1366 imapfolder = IMAP_FOLDER(folder);
1368 separator = imap_get_path_separator(imapfolder, item->path);
1370 if (folder->ui_func)
1371 folder->ui_func(folder, item, folder->ui_func_data);
1374 wildcard[0] = separator;
1377 real_path = imap_get_real_path(imapfolder, item->path);
1381 real_path = g_strdup("");
1384 Xstrcat_a(wildcard_path, real_path, wildcard,
1385 {g_free(real_path); return IMAP_ERROR;});
1386 QUOTE_IF_REQUIRED(wildcard_path, wildcard_path);
1388 imap_gen_send(session, "LIST \"\" %s",
1391 strtailchomp(real_path, separator);
1392 item_list = imap_parse_list(imapfolder, session, real_path, NULL);
1395 node = item->node->children;
1396 while (node != NULL) {
1397 FolderItem *old_item = FOLDER_ITEM(node->data);
1398 GNode *next = node->next;
1401 for (cur = item_list; cur != NULL; cur = cur->next) {
1402 FolderItem *cur_item = FOLDER_ITEM(cur->data);
1403 if (!strcmp2(old_item->path, cur_item->path)) {
1404 new_item = cur_item;
1409 debug_print("folder '%s' not found. removing...\n",
1411 folder_item_remove(old_item);
1413 old_item->no_sub = new_item->no_sub;
1414 old_item->no_select = new_item->no_select;
1415 if (old_item->no_sub == TRUE && node->children) {
1416 debug_print("folder '%s' doesn't have "
1417 "subfolders. removing...\n",
1419 folder_item_remove_children(old_item);
1426 for (cur = item_list; cur != NULL; cur = cur->next) {
1427 FolderItem *cur_item = FOLDER_ITEM(cur->data);
1429 for (node = item->node->children; node != NULL;
1430 node = node->next) {
1431 if (!strcmp2(FOLDER_ITEM(node->data)->path,
1433 new_item = FOLDER_ITEM(node->data);
1434 folder_item_destroy(cur_item);
1440 new_item = cur_item;
1441 debug_print("new folder '%s' found.\n", new_item->path);
1442 folder_item_append(item, new_item);
1445 if (!strcmp(new_item->path, "INBOX")) {
1446 new_item->stype = F_INBOX;
1447 folder->inbox = new_item;
1448 } else if (!folder_item_parent(item) || item->stype == F_INBOX) {
1451 base = g_path_get_basename(new_item->path);
1453 if (!folder->outbox && !g_ascii_strcasecmp(base, "Sent")) {
1454 new_item->stype = F_OUTBOX;
1455 folder->outbox = new_item;
1456 } else if (!folder->draft && !g_ascii_strcasecmp(base, "Drafts")) {
1457 new_item->stype = F_DRAFT;
1458 folder->draft = new_item;
1459 } else if (!folder->queue && !g_ascii_strcasecmp(base, "Queue")) {
1460 new_item->stype = F_QUEUE;
1461 folder->queue = new_item;
1462 } else if (!folder->trash && !g_ascii_strcasecmp(base, "Trash")) {
1463 new_item->stype = F_TRASH;
1464 folder->trash = new_item;
1469 if (new_item->no_sub == FALSE)
1470 imap_scan_tree_recursive(session, new_item);
1473 g_slist_free(item_list);
1475 return IMAP_SUCCESS;
1478 static GSList *imap_parse_list(IMAPFolder *folder, IMAPSession *session,
1479 const gchar *real_path, gchar *separator)
1481 gchar buf[IMAPBUFSIZE];
1483 gchar separator_str[16];
1486 gchar *loc_name, *loc_path;
1487 GSList *item_list = NULL;
1489 FolderItem *new_item;
1491 debug_print("getting list of %s ...\n",
1492 *real_path ? real_path : "\"\"");
1494 str = g_string_new(NULL);
1497 if (sock_gets(SESSION(session)->sock, buf, sizeof(buf)) <= 0) {
1498 log_warning(_("error occurred while getting LIST.\n"));
1502 if (buf[0] != '*' || buf[1] != ' ') {
1503 log_print("IMAP4< %s\n", buf);
1504 if (sscanf(buf, "%*d %16s", buf) < 1 ||
1505 strcmp(buf, "OK") != 0)
1506 log_warning(_("error occurred while getting LIST.\n"));
1510 debug_print("IMAP4< %s\n", buf);
1512 g_string_assign(str, buf);
1514 if (strncmp(p, "LIST ", 5) != 0) continue;
1517 if (*p != '(') continue;
1519 p = strchr_cpy(p, ')', flags, sizeof(flags));
1521 while (*p == ' ') p++;
1523 p = strchr_cpy(p, ' ', separator_str, sizeof(separator_str));
1525 extract_quote(separator_str, '"');
1526 if (!strcmp(separator_str, "NIL"))
1527 separator_str[0] = '\0';
1529 *separator = separator_str[0];
1532 while (*p == ' ') p++;
1533 if (*p == '{' || *p == '"')
1534 p = imap_parse_atom(SESSION(session)->sock, p,
1535 buf, sizeof(buf), str);
1537 strncpy2(buf, p, sizeof(buf));
1538 strtailchomp(buf, separator_str[0]);
1539 if (buf[0] == '\0') continue;
1540 if (!strcmp(buf, real_path)) continue;
1542 if (separator_str[0] != '\0')
1543 subst_char(buf, separator_str[0], '/');
1544 base = g_path_get_basename(buf);
1545 if (base[0] == '.') continue;
1547 loc_name = imap_modified_utf7_to_utf8(base);
1548 loc_path = imap_modified_utf7_to_utf8(buf);
1549 new_item = folder_item_new(FOLDER(folder), loc_name, loc_path);
1550 if (strcasestr(flags, "\\Noinferiors") != NULL)
1551 new_item->no_sub = TRUE;
1552 if (strcmp(buf, "INBOX") != 0 &&
1553 strcasestr(flags, "\\Noselect") != NULL)
1554 new_item->no_select = TRUE;
1556 item_list = g_slist_append(item_list, new_item);
1558 debug_print("folder '%s' found.\n", loc_path);
1564 g_string_free(str, TRUE);
1569 static gint imap_create_tree(Folder *folder)
1571 g_return_val_if_fail(folder != NULL, -1);
1572 g_return_val_if_fail(folder->node != NULL, -1);
1573 g_return_val_if_fail(folder->node->data != NULL, -1);
1574 g_return_val_if_fail(folder->account != NULL, -1);
1576 imap_scan_tree(folder);
1577 imap_create_missing_folders(folder);
1582 static void imap_create_missing_folders(Folder *folder)
1584 g_return_if_fail(folder != NULL);
1587 folder->inbox = imap_create_special_folder
1588 (folder, F_INBOX, "INBOX");
1590 if (!folder->outbox)
1591 folder->outbox = imap_create_special_folder
1592 (folder, F_OUTBOX, "Sent");
1594 folder->draft = imap_create_special_folder
1595 (folder, F_DRAFT, "Drafts");
1597 folder->queue = imap_create_special_folder
1598 (folder, F_QUEUE, "Queue");
1601 folder->trash = imap_create_special_folder
1602 (folder, F_TRASH, "Trash");
1605 static FolderItem *imap_create_special_folder(Folder *folder,
1606 SpecialFolderItemType stype,
1610 FolderItem *new_item;
1612 g_return_val_if_fail(folder != NULL, NULL);
1613 g_return_val_if_fail(folder->node != NULL, NULL);
1614 g_return_val_if_fail(folder->node->data != NULL, NULL);
1615 g_return_val_if_fail(folder->account != NULL, NULL);
1616 g_return_val_if_fail(name != NULL, NULL);
1618 item = FOLDER_ITEM(folder->node->data);
1619 new_item = imap_create_folder(folder, item, name);
1622 g_warning("Can't create '%s'\n", name);
1623 if (!folder->inbox) return NULL;
1625 new_item = imap_create_folder(folder, folder->inbox, name);
1627 g_warning("Can't create '%s' under INBOX\n", name);
1629 new_item->stype = stype;
1631 new_item->stype = stype;
1636 static gchar *imap_folder_get_path(Folder *folder)
1640 g_return_val_if_fail(folder != NULL, NULL);
1641 g_return_val_if_fail(folder->account != NULL, NULL);
1643 folder_path = g_strconcat(get_imap_cache_dir(),
1645 folder->account->recv_server,
1647 folder->account->userid,
1653 static gchar *imap_item_get_path(Folder *folder, FolderItem *item)
1655 gchar *folder_path, *path;
1657 g_return_val_if_fail(folder != NULL, NULL);
1658 g_return_val_if_fail(item != NULL, NULL);
1659 folder_path = imap_folder_get_path(folder);
1661 g_return_val_if_fail(folder_path != NULL, NULL);
1662 if (folder_path[0] == G_DIR_SEPARATOR) {
1664 path = g_strconcat(folder_path, G_DIR_SEPARATOR_S,
1667 path = g_strdup(folder_path);
1670 path = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S,
1671 folder_path, G_DIR_SEPARATOR_S,
1674 path = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S,
1677 g_free(folder_path);
1682 static FolderItem *imap_create_folder(Folder *folder, FolderItem *parent,
1685 gchar *dirpath, *imap_path;
1686 IMAPSession *session;
1687 FolderItem *new_item;
1693 g_return_val_if_fail(folder != NULL, NULL);
1694 g_return_val_if_fail(folder->account != NULL, NULL);
1695 g_return_val_if_fail(parent != NULL, NULL);
1696 g_return_val_if_fail(name != NULL, NULL);
1698 session = imap_session_get(folder);
1699 if (!session) return NULL;
1701 if (!folder_item_parent(parent) && strcmp(name, "INBOX") == 0)
1702 dirpath = g_strdup(name);
1703 else if (parent->path)
1704 dirpath = g_strconcat(parent->path, "/", name, NULL);
1705 else if ((p = strchr(name, '/')) != NULL && *(p + 1) != '\0')
1706 dirpath = g_strdup(name);
1707 else if (folder->account->imap_dir && *folder->account->imap_dir) {
1710 Xstrdup_a(imap_dir, folder->account->imap_dir, return NULL);
1711 strtailchomp(imap_dir, '/');
1712 dirpath = g_strconcat(imap_dir, "/", name, NULL);
1714 dirpath = g_strdup(name);
1716 /* keep trailing directory separator to create a folder that contains
1718 imap_path = imap_utf8_to_modified_utf7(dirpath);
1719 strtailchomp(dirpath, '/');
1720 Xstrdup_a(new_name, name, {g_free(dirpath); return NULL;});
1721 strtailchomp(new_name, '/');
1722 separator = imap_get_path_separator(IMAP_FOLDER(folder), imap_path);
1723 imap_path_separator_subst(imap_path, separator);
1724 subst_char(new_name, '/', separator);
1726 if (strcmp(name, "INBOX") != 0) {
1729 gboolean exist = FALSE;
1731 argbuf = g_ptr_array_new();
1732 ok = imap_cmd_list(session, NULL, imap_path,
1734 if (ok != IMAP_SUCCESS) {
1735 log_warning(_("can't create mailbox: LIST failed\n"));
1738 ptr_array_free_strings(argbuf);
1739 g_ptr_array_free(argbuf, TRUE);
1743 for (i = 0; i < argbuf->len; i++) {
1745 str = g_ptr_array_index(argbuf, i);
1746 if (!strncmp(str, "LIST ", 5)) {
1751 ptr_array_free_strings(argbuf);
1752 g_ptr_array_free(argbuf, TRUE);
1755 ok = imap_cmd_create(session, imap_path);
1756 if (ok != IMAP_SUCCESS) {
1757 log_warning(_("can't create mailbox\n"));
1765 new_item = folder_item_new(folder, new_name, dirpath);
1766 folder_item_append(parent, new_item);
1770 dirpath = folder_item_get_path(new_item);
1771 if (!is_dir_exist(dirpath))
1772 make_dir_hier(dirpath);
1778 static gint imap_rename_folder(Folder *folder, FolderItem *item,
1783 gchar *real_oldpath;
1784 gchar *real_newpath;
1786 gchar *old_cache_dir;
1787 gchar *new_cache_dir;
1788 IMAPSession *session;
1791 gint exists, recent, unseen;
1792 guint32 uid_validity;
1794 g_return_val_if_fail(folder != NULL, -1);
1795 g_return_val_if_fail(item != NULL, -1);
1796 g_return_val_if_fail(item->path != NULL, -1);
1797 g_return_val_if_fail(name != NULL, -1);
1799 if (strchr(name, imap_get_path_separator(IMAP_FOLDER(folder), item->path)) != NULL) {
1800 g_warning(_("New folder name must not contain the namespace "
1805 session = imap_session_get(folder);
1806 if (!session) return -1;
1808 real_oldpath = imap_get_real_path(IMAP_FOLDER(folder), item->path);
1810 g_free(session->mbox);
1811 session->mbox = NULL;
1812 ok = imap_cmd_examine(session, "INBOX",
1813 &exists, &recent, &unseen, &uid_validity);
1814 if (ok != IMAP_SUCCESS) {
1815 g_free(real_oldpath);
1819 separator = imap_get_path_separator(IMAP_FOLDER(folder), item->path);
1820 if (strchr(item->path, G_DIR_SEPARATOR)) {
1821 dirpath = g_path_get_dirname(item->path);
1822 newpath = g_strconcat(dirpath, G_DIR_SEPARATOR_S, name, NULL);
1825 newpath = g_strdup(name);
1827 real_newpath = imap_utf8_to_modified_utf7(newpath);
1828 imap_path_separator_subst(real_newpath, separator);
1830 ok = imap_cmd_rename(session, real_oldpath, real_newpath);
1831 if (ok != IMAP_SUCCESS) {
1832 log_warning(_("can't rename mailbox: %s to %s\n"),
1833 real_oldpath, real_newpath);
1834 g_free(real_oldpath);
1836 g_free(real_newpath);
1841 item->name = g_strdup(name);
1843 old_cache_dir = folder_item_get_path(item);
1845 paths[0] = g_strdup(item->path);
1847 g_node_traverse(item->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
1848 imap_rename_folder_func, paths);
1850 if (is_dir_exist(old_cache_dir)) {
1851 new_cache_dir = folder_item_get_path(item);
1852 if (rename(old_cache_dir, new_cache_dir) < 0) {
1853 FILE_OP_ERROR(old_cache_dir, "rename");
1855 g_free(new_cache_dir);
1858 g_free(old_cache_dir);
1861 g_free(real_oldpath);
1862 g_free(real_newpath);
1867 static gint imap_remove_folder(Folder *folder, FolderItem *item)
1870 IMAPSession *session;
1873 gint exists, recent, unseen;
1874 guint32 uid_validity;
1876 g_return_val_if_fail(folder != NULL, -1);
1877 g_return_val_if_fail(item != NULL, -1);
1878 g_return_val_if_fail(item->path != NULL, -1);
1880 session = imap_session_get(folder);
1881 if (!session) return -1;
1883 path = imap_get_real_path(IMAP_FOLDER(folder), item->path);
1885 ok = imap_cmd_examine(session, "INBOX",
1886 &exists, &recent, &unseen, &uid_validity);
1887 if (ok != IMAP_SUCCESS) {
1892 ok = imap_cmd_delete(session, path);
1893 if (ok != IMAP_SUCCESS) {
1894 log_warning(_("can't delete mailbox\n"));
1900 cache_dir = folder_item_get_path(item);
1901 if (is_dir_exist(cache_dir) && remove_dir_recursive(cache_dir) < 0)
1902 g_warning("can't remove directory '%s'\n", cache_dir);
1904 folder_item_remove(item);
1909 typedef struct _uncached_data {
1910 IMAPSession *session;
1912 MsgNumberList *numlist;
1916 static void *imap_get_uncached_messages_thread(void *data)
1918 uncached_data *stuff = (uncached_data *)data;
1919 IMAPSession *session = stuff->session;
1920 FolderItem *item = stuff->item;
1921 MsgNumberList *numlist = stuff->numlist;
1924 GSList *newlist = NULL;
1925 GSList *llast = NULL;
1928 GSList *seq_list, *cur;
1931 if (session == NULL || item == NULL || item->folder == NULL
1932 || FOLDER_CLASS(item->folder) != &imap_class) {
1937 seq_list = imap_get_seq_set_from_numlist(numlist);
1938 for (cur = seq_list; cur != NULL; cur = g_slist_next(cur)) {
1939 imapset = cur->data;
1941 if (imap_cmd_envelope(session, imapset)
1943 log_warning(_("can't get envelope\n"));
1947 str = g_string_new(NULL);
1950 if ((tmp =sock_getline(SESSION(session)->sock)) == NULL) {
1951 log_warning(_("error occurred while getting envelope.\n"));
1952 g_string_free(str, TRUE);
1956 if (tmp[0] != '*' || tmp[1] != ' ') {
1957 log_print("IMAP4< %s\n", tmp);
1961 if (strstr(tmp, "FETCH") == NULL) {
1962 log_print("IMAP4< %s\n", tmp);
1966 log_print("IMAP4< %s\n", tmp);
1967 g_string_assign(str, tmp);
1970 msginfo = imap_parse_envelope
1971 (SESSION(session)->sock, item, str);
1973 log_warning(_("can't parse envelope: %s\n"), str->str);
1976 if (item->stype == F_QUEUE) {
1977 MSG_SET_TMP_FLAGS(msginfo->flags, MSG_QUEUED);
1978 } else if (item->stype == F_DRAFT) {
1979 MSG_SET_TMP_FLAGS(msginfo->flags, MSG_DRAFT);
1982 msginfo->folder = item;
1985 llast = newlist = g_slist_append(newlist, msginfo);
1987 llast = g_slist_append(llast, msginfo);
1988 llast = llast->next;
1992 g_string_free(str, TRUE);
1994 imap_seq_set_free(seq_list);
1996 session_set_access_time(SESSION(session));
2002 static GSList *imap_get_uncached_messages(IMAPSession *session,
2004 MsgNumberList *numlist)
2006 uncached_data *data = g_new0(uncached_data, 1);
2007 GSList *result = NULL;
2012 data->session = session;
2014 data->numlist = numlist;
2015 #if (defined USE_PTHREAD && defined __GLIBC__ && (__GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 3)))
2016 if (pthread_create(&pt, PTHREAD_CREATE_JOINABLE,
2017 imap_get_uncached_messages_thread, data) != 0) {
2018 result = (GSList *)imap_get_uncached_messages_thread(data);
2022 debug_print("waiting for imap_get_uncached_messages_thread...\n");
2023 while(!data->done) {
2024 /* don't let the interface freeze while waiting */
2027 debug_print("imap_get_uncached_messages_thread done\n");
2029 /* get the thread's return value and clean its resources */
2030 pthread_join(pt, (void *)&result);
2032 result = (GSList *)imap_get_uncached_messages_thread(data);
2038 static void imap_delete_all_cached_messages(FolderItem *item)
2042 g_return_if_fail(item != NULL);
2043 g_return_if_fail(item->folder != NULL);
2044 g_return_if_fail(FOLDER_CLASS(item->folder) == &imap_class);
2046 debug_print("Deleting all cached messages...\n");
2048 dir = folder_item_get_path(item);
2049 if (is_dir_exist(dir))
2050 remove_all_numbered_files(dir);
2053 debug_print("done.\n");
2057 static SockInfo *imap_open_tunnel(const gchar *server,
2058 const gchar *tunnelcmd,
2061 static SockInfo *imap_open_tunnel(const gchar *server,
2062 const gchar *tunnelcmd)
2067 if ((sock = sock_connect_cmd(server, tunnelcmd)) == NULL) {
2068 log_warning(_("Can't establish IMAP4 session with: %s\n"),
2073 return imap_init_sock(sock, ssl_type);
2075 return imap_init_sock(sock);
2081 void *imap_open_thread(void *data)
2083 SockInfo *sock = NULL;
2084 thread_data *td = (thread_data *)data;
2085 if ((sock = sock_connect(td->server, td->port)) == NULL) {
2086 log_warning(_("Can't connect to IMAP4 server: %s:%d\n"),
2087 td->server, td->port);
2093 if (td->ssl_type == SSL_TUNNEL && !ssl_init_socket(sock)) {
2094 log_warning(_("Can't establish IMAP4 session with: %s:%d\n"),
2095 td->server, td->port);
2108 static SockInfo *imap_open_blocking(const gchar *server, gushort port,
2111 static SockInfo *imap_open_blocking(const gchar *server, gushort port)
2115 if ((sock = sock_connect(server, port)) == NULL) {
2116 log_warning(_("Can't connect to IMAP4 server: %s:%d\n"),
2122 if (ssl_type == SSL_TUNNEL && !ssl_init_socket(sock)) {
2123 log_warning(_("Can't establish IMAP4 session with: %s:%d\n"),
2134 static SockInfo *imap_open(const gchar *server, gushort port,
2137 static SockInfo *imap_open(const gchar *server, gushort port)
2140 #if (defined USE_PTHREAD && defined __GLIBC__ && (__GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 3)))
2141 /* non blocking stuff */
2142 thread_data *td = g_new0(thread_data, 1);
2144 SockInfo *sock = NULL;
2147 td->ssl_type = ssl_type;
2149 td->server = g_strdup(server);
2153 statusbar_print_all(_("Connecting to IMAP4 server: %s..."), server);
2155 debug_print("creating imap_open_thread...\n");
2156 if (pthread_create(&pt, PTHREAD_CREATE_JOINABLE,
2157 imap_open_thread, td) != 0) {
2158 statusbar_pop_all();
2162 return imap_open_blocking(server, port, ssl_type);
2164 return imap_open_blocking(server, port);
2168 debug_print("waiting for imap_open_thread...\n");
2170 /* don't let the interface freeze while waiting */
2174 /* get the thread's return value and clean its resources */
2175 pthread_join(pt, (void *)&sock);
2179 debug_print("imap_open_thread returned %p\n", sock);
2180 statusbar_pop_all();
2184 return imap_open_blocking(server, port, ssl_type);
2186 return imap_open_blocking(server, port);
2192 static SockInfo *imap_init_sock(SockInfo *sock, SSLType ssl_type)
2194 static SockInfo *imap_init_sock(SockInfo *sock)
2201 static GList *imap_parse_namespace_str(gchar *str)
2206 IMAPNameSpace *namespace;
2207 GList *ns_list = NULL;
2209 while (*p != '\0') {
2210 /* parse ("#foo" "/") */
2212 while (*p && *p != '(') p++;
2213 if (*p == '\0') break;
2216 while (*p && *p != '"') p++;
2217 if (*p == '\0') break;
2221 while (*p && *p != '"') p++;
2222 if (*p == '\0') break;
2226 while (*p && isspace(*p)) p++;
2227 if (*p == '\0') break;
2228 if (strncmp(p, "NIL", 3) == 0)
2230 else if (*p == '"') {
2233 while (*p && *p != '"') p++;
2234 if (*p == '\0') break;
2239 while (*p && *p != ')') p++;
2240 if (*p == '\0') break;
2243 namespace = g_new(IMAPNameSpace, 1);
2244 namespace->name = g_strdup(name);
2245 namespace->separator = separator ? separator[0] : '\0';
2246 ns_list = g_list_append(ns_list, namespace);
2252 static void imap_parse_namespace(IMAPSession *session, IMAPFolder *folder)
2257 g_return_if_fail(session != NULL);
2258 g_return_if_fail(folder != NULL);
2260 if (folder->ns_personal != NULL ||
2261 folder->ns_others != NULL ||
2262 folder->ns_shared != NULL)
2265 if (!imap_has_capability(session, "NAMESPACE")) {
2266 imap_get_namespace_by_list(session, folder);
2270 if (imap_cmd_namespace(session, &ns_str)
2272 log_warning(_("can't get namespace\n"));
2276 str_array = strsplit_parenthesis(ns_str, '(', ')', 3);
2277 if (str_array == NULL) {
2279 imap_get_namespace_by_list(session, folder);
2283 folder->ns_personal = imap_parse_namespace_str(str_array[0]);
2284 if (str_array[0] && str_array[1])
2285 folder->ns_others = imap_parse_namespace_str(str_array[1]);
2286 if (str_array[0] && str_array[1] && str_array[2])
2287 folder->ns_shared = imap_parse_namespace_str(str_array[2]);
2288 g_strfreev(str_array);
2292 static void imap_get_namespace_by_list(IMAPSession *session, IMAPFolder *folder)
2294 GSList *item_list, *cur;
2295 gchar separator = '\0';
2296 IMAPNameSpace *namespace;
2298 g_return_if_fail(session != NULL);
2299 g_return_if_fail(folder != NULL);
2301 if (folder->ns_personal != NULL ||
2302 folder->ns_others != NULL ||
2303 folder->ns_shared != NULL)
2306 imap_gen_send(session, "LIST \"\" \"\"");
2307 item_list = imap_parse_list(folder, session, "", &separator);
2308 for (cur = item_list; cur != NULL; cur = cur->next)
2309 folder_item_destroy(FOLDER_ITEM(cur->data));
2310 g_slist_free(item_list);
2312 namespace = g_new(IMAPNameSpace, 1);
2313 namespace->name = g_strdup("");
2314 namespace->separator = separator;
2315 folder->ns_personal = g_list_append(NULL, namespace);
2318 static IMAPNameSpace *imap_find_namespace_from_list(GList *ns_list,
2321 IMAPNameSpace *namespace = NULL;
2322 gchar *tmp_path, *name;
2324 if (!path) path = "";
2326 for (; ns_list != NULL; ns_list = ns_list->next) {
2327 IMAPNameSpace *tmp_ns = ns_list->data;
2329 Xstrcat_a(tmp_path, path, "/", return namespace);
2330 Xstrdup_a(name, tmp_ns->name, return namespace);
2331 if (tmp_ns->separator && tmp_ns->separator != '/') {
2332 subst_char(tmp_path, tmp_ns->separator, '/');
2333 subst_char(name, tmp_ns->separator, '/');
2335 if (strncmp(tmp_path, name, strlen(name)) == 0)
2342 static IMAPNameSpace *imap_find_namespace(IMAPFolder *folder,
2345 IMAPNameSpace *namespace;
2347 g_return_val_if_fail(folder != NULL, NULL);
2349 namespace = imap_find_namespace_from_list(folder->ns_personal, path);
2350 if (namespace) return namespace;
2351 namespace = imap_find_namespace_from_list(folder->ns_others, path);
2352 if (namespace) return namespace;
2353 namespace = imap_find_namespace_from_list(folder->ns_shared, path);
2354 if (namespace) return namespace;
2359 static gchar imap_get_path_separator(IMAPFolder *folder, const gchar *path)
2361 IMAPNameSpace *namespace;
2362 gchar separator = '/';
2364 namespace = imap_find_namespace(folder, path);
2365 if (namespace && namespace->separator)
2366 separator = namespace->separator;
2371 static gchar *imap_get_real_path(IMAPFolder *folder, const gchar *path)
2376 g_return_val_if_fail(folder != NULL, NULL);
2377 g_return_val_if_fail(path != NULL, NULL);
2379 real_path = imap_utf8_to_modified_utf7(path);
2380 separator = imap_get_path_separator(folder, path);
2381 imap_path_separator_subst(real_path, separator);
2386 static gchar *imap_parse_atom(SockInfo *sock, gchar *src,
2387 gchar *dest, gint dest_len, GString *str)
2389 gchar *cur_pos = src;
2392 g_return_val_if_fail(str != NULL, cur_pos);
2394 /* read the next line if the current response buffer is empty */
2395 while (isspace(*(guchar *)cur_pos)) cur_pos++;
2396 while (*cur_pos == '\0') {
2397 if ((nextline = imap_getline(sock)) == NULL)
2399 g_string_assign(str, nextline);
2401 strretchomp(nextline);
2402 /* log_print("IMAP4< %s\n", nextline); */
2403 debug_print("IMAP4< %s\n", nextline);
2406 while (isspace(*(guchar *)cur_pos)) cur_pos++;
2409 if (!strncmp(cur_pos, "NIL", 3)) {
2412 } else if (*cur_pos == '\"') {
2415 p = get_quoted(cur_pos, '\"', dest, dest_len);
2416 cur_pos = p ? p : cur_pos + 2;
2417 } else if (*cur_pos == '{') {
2422 cur_pos = strchr_cpy(cur_pos + 1, '}', buf, sizeof(buf));
2424 g_return_val_if_fail(len >= 0, cur_pos);
2426 g_string_truncate(str, 0);
2430 if ((nextline = imap_getline(sock)) == NULL)
2432 line_len += strlen(nextline);
2433 g_string_append(str, nextline);
2435 strretchomp(nextline);
2436 /* log_print("IMAP4< %s\n", nextline); */
2437 debug_print("IMAP4< %s\n", nextline);
2439 } while (line_len < len);
2441 memcpy(dest, cur_pos, MIN(len, dest_len - 1));
2442 dest[MIN(len, dest_len - 1)] = '\0';
2449 static gchar *imap_get_header(SockInfo *sock, gchar *cur_pos, gchar **headers,
2459 g_return_val_if_fail(str != NULL, cur_pos);
2461 while (isspace(*(guchar *)cur_pos)) cur_pos++;
2463 g_return_val_if_fail(*cur_pos == '{', cur_pos);
2465 cur_pos = strchr_cpy(cur_pos + 1, '}', buf, sizeof(buf));
2467 g_return_val_if_fail(len >= 0, cur_pos);
2469 g_string_truncate(str, 0);
2473 if ((nextline = sock_getline(sock)) == NULL)
2475 block_len += strlen(nextline);
2476 g_string_append(str, nextline);
2478 strretchomp(nextline);
2479 /* debug_print("IMAP4< %s\n", nextline); */
2481 } while (block_len < len);
2483 debug_print("IMAP4< [contents of RFC822.HEADER]\n");
2485 *headers = g_strndup(cur_pos, len);
2486 debug_print("IMAP4< %s ---\n", *headers);
2489 while (isspace(*(guchar *)cur_pos)) cur_pos++;
2490 while (*cur_pos == '\0') {
2491 if ((nextline = sock_getline(sock)) == NULL)
2493 g_string_assign(str, nextline);
2495 strretchomp(nextline);
2496 debug_print("IMAP4< %s\n", nextline);
2499 while (isspace(*(guchar *)cur_pos)) cur_pos++;
2505 static MsgFlags imap_parse_flags(const gchar *flag_str)
2507 const gchar *p = flag_str;
2508 MsgFlags flags = {0, 0};
2510 flags.perm_flags = MSG_UNREAD;
2512 while ((p = strchr(p, '\\')) != NULL) {
2515 if (g_ascii_strncasecmp(p, "Recent", 6) == 0 && MSG_IS_UNREAD(flags)) {
2516 MSG_SET_PERM_FLAGS(flags, MSG_NEW);
2517 } else if (g_ascii_strncasecmp(p, "Seen", 4) == 0) {
2518 MSG_UNSET_PERM_FLAGS(flags, MSG_NEW|MSG_UNREAD);
2519 } else if (g_ascii_strncasecmp(p, "Deleted", 7) == 0) {
2520 MSG_SET_PERM_FLAGS(flags, MSG_DELETED);
2521 } else if (g_ascii_strncasecmp(p, "Flagged", 7) == 0) {
2522 MSG_SET_PERM_FLAGS(flags, MSG_MARKED);
2523 } else if (g_ascii_strncasecmp(p, "Answered", 8) == 0) {
2524 MSG_SET_PERM_FLAGS(flags, MSG_REPLIED);
2531 static MsgInfo *imap_parse_envelope(SockInfo *sock, FolderItem *item,
2534 gchar buf[IMAPBUFSIZE];
2535 MsgInfo *msginfo = NULL;
2540 MsgFlags flags = {0, 0}, imap_flags = {0, 0};
2542 g_return_val_if_fail(line_str != NULL, NULL);
2543 g_return_val_if_fail(line_str->str[0] == '*' &&
2544 line_str->str[1] == ' ', NULL);
2546 MSG_SET_TMP_FLAGS(flags, MSG_IMAP);
2547 if (item->stype == F_QUEUE) {
2548 MSG_SET_TMP_FLAGS(flags, MSG_QUEUED);
2549 } else if (item->stype == F_DRAFT) {
2550 MSG_SET_TMP_FLAGS(flags, MSG_DRAFT);
2553 cur_pos = line_str->str + 2;
2555 #define PARSE_ONE_ELEMENT(ch) \
2557 cur_pos = strchr_cpy(cur_pos, ch, buf, sizeof(buf)); \
2558 if (cur_pos == NULL) { \
2559 g_warning("cur_pos == NULL\n"); \
2560 procmsg_msginfo_free(msginfo); \
2565 PARSE_ONE_ELEMENT(' ');
2568 PARSE_ONE_ELEMENT(' ');
2569 g_return_val_if_fail(!strcmp(buf, "FETCH"), NULL);
2571 g_return_val_if_fail(*cur_pos == '(', NULL);
2574 while (*cur_pos != '\0' && *cur_pos != ')') {
2575 while (*cur_pos == ' ') cur_pos++;
2577 if (!strncmp(cur_pos, "UID ", 4)) {
2579 uid = strtoul(cur_pos, &cur_pos, 10);
2580 } else if (!strncmp(cur_pos, "FLAGS ", 6)) {
2582 if (*cur_pos != '(') {
2583 g_warning("*cur_pos != '('\n");
2584 procmsg_msginfo_free(msginfo);
2588 PARSE_ONE_ELEMENT(')');
2589 imap_flags = imap_parse_flags(buf);
2590 } else if (!strncmp(cur_pos, "RFC822.SIZE ", 12)) {
2592 size = strtol(cur_pos, &cur_pos, 10);
2593 } else if (!strncmp(cur_pos, "RFC822.HEADER ", 14)) {
2597 cur_pos = imap_get_header(sock, cur_pos, &headers,
2599 msginfo = procheader_parse_str(headers, flags, FALSE, FALSE);
2602 g_warning("invalid FETCH response: %s\n", cur_pos);
2608 msginfo->msgnum = uid;
2609 msginfo->size = size;
2610 msginfo->flags.tmp_flags |= imap_flags.tmp_flags;
2611 msginfo->flags.perm_flags = imap_flags.perm_flags;
2617 static gchar *imap_get_flag_str(IMAPFlags flags)
2622 str = g_string_new(NULL);
2624 if (IMAP_IS_SEEN(flags)) g_string_append(str, "\\Seen ");
2625 if (IMAP_IS_ANSWERED(flags)) g_string_append(str, "\\Answered ");
2626 if (IMAP_IS_FLAGGED(flags)) g_string_append(str, "\\Flagged ");
2627 if (IMAP_IS_DELETED(flags)) g_string_append(str, "\\Deleted ");
2628 if (IMAP_IS_DRAFT(flags)) g_string_append(str, "\\Draft");
2630 if (str->len > 0 && str->str[str->len - 1] == ' ')
2631 g_string_truncate(str, str->len - 1);
2634 g_string_free(str, FALSE);
2639 static gint imap_set_message_flags(IMAPSession *session,
2640 MsgNumberList *numlist,
2647 GSList *seq_list, *cur;
2650 flag_str = imap_get_flag_str(flags);
2651 cmd = g_strconcat(is_set ? "+FLAGS.SILENT (" : "-FLAGS.SILENT (",
2652 flag_str, ")", NULL);
2655 seq_list = imap_get_seq_set_from_numlist(numlist);
2656 for (cur = seq_list; cur != NULL; cur = g_slist_next(cur)) {
2657 imapset = cur->data;
2659 ok = imap_cmd_store(session, imapset, cmd);
2661 imap_seq_set_free(seq_list);
2667 static gint imap_select(IMAPSession *session, IMAPFolder *folder,
2669 gint *exists, gint *recent, gint *unseen,
2670 guint32 *uid_validity)
2674 gint exists_, recent_, unseen_;
2675 guint32 uid_validity_;
2677 if (!exists || !recent || !unseen || !uid_validity) {
2678 if (session->mbox && strcmp(session->mbox, path) == 0)
2679 return IMAP_SUCCESS;
2683 uid_validity = &uid_validity_;
2686 g_free(session->mbox);
2687 session->mbox = NULL;
2689 real_path = imap_get_real_path(folder, path);
2690 ok = imap_cmd_select(session, real_path,
2691 exists, recent, unseen, uid_validity);
2692 if (ok != IMAP_SUCCESS)
2693 log_warning(_("can't select folder: %s\n"), real_path);
2695 session->mbox = g_strdup(path);
2696 session->folder_content_changed = FALSE;
2703 #define THROW(err) { ok = err; goto catch; }
2705 static gint imap_status(IMAPSession *session, IMAPFolder *folder,
2707 gint *messages, gint *recent,
2708 guint32 *uid_next, guint32 *uid_validity,
2714 GPtrArray *argbuf = NULL;
2717 if (messages && recent && uid_next && uid_validity && unseen) {
2718 *messages = *recent = *uid_next = *uid_validity = *unseen = 0;
2719 argbuf = g_ptr_array_new();
2722 real_path = imap_get_real_path(folder, path);
2723 QUOTE_IF_REQUIRED(real_path_, real_path);
2724 imap_gen_send(session, "STATUS %s "
2725 "(MESSAGES RECENT UIDNEXT UIDVALIDITY UNSEEN)",
2728 ok = imap_cmd_ok(session, argbuf);
2729 if (ok != IMAP_SUCCESS || !argbuf) THROW(ok);
2731 str = search_array_str(argbuf, "STATUS");
2732 if (!str) THROW(IMAP_ERROR);
2734 str = strchr(str, '(');
2735 if (!str) THROW(IMAP_ERROR);
2737 while (*str != '\0' && *str != ')') {
2738 while (*str == ' ') str++;
2740 if (!strncmp(str, "MESSAGES ", 9)) {
2742 *messages = strtol(str, &str, 10);
2743 } else if (!strncmp(str, "RECENT ", 7)) {
2745 *recent = strtol(str, &str, 10);
2746 } else if (!strncmp(str, "UIDNEXT ", 8)) {
2748 *uid_next = strtoul(str, &str, 10);
2749 } else if (!strncmp(str, "UIDVALIDITY ", 12)) {
2751 *uid_validity = strtoul(str, &str, 10);
2752 } else if (!strncmp(str, "UNSEEN ", 7)) {
2754 *unseen = strtol(str, &str, 10);
2756 g_warning("invalid STATUS response: %s\n", str);
2764 ptr_array_free_strings(argbuf);
2765 g_ptr_array_free(argbuf, TRUE);
2773 static gboolean imap_has_capability(IMAPSession *session, const gchar *cap)
2777 for (p = session->capability; *p != NULL; ++p) {
2778 if (!g_ascii_strcasecmp(*p, cap))
2785 static void imap_free_capabilities(IMAPSession *session)
2787 g_strfreev(session->capability);
2788 session->capability = NULL;
2791 /* low-level IMAP4rev1 commands */
2793 static gint imap_cmd_authenticate(IMAPSession *session, const gchar *user,
2794 const gchar *pass, IMAPAuthType type)
2801 gchar hexdigest[33];
2805 auth_type = "CRAM-MD5";
2807 imap_gen_send(session, "AUTHENTICATE %s", auth_type);
2808 ok = imap_gen_recv(session, &buf);
2809 if (ok != IMAP_SUCCESS || buf[0] != '+' || buf[1] != ' ') {
2814 challenge = g_malloc(strlen(buf + 2) + 1);
2815 challenge_len = base64_decode(challenge, buf + 2, -1);
2816 challenge[challenge_len] = '\0';
2818 log_print("IMAP< [Decoded: %s]\n", challenge);
2820 md5_hex_hmac(hexdigest, challenge, challenge_len, pass, strlen(pass));
2823 response = g_strdup_printf("%s %s", user, hexdigest);
2824 log_print("IMAP> [Encoded: %s]\n", response);
2825 response64 = g_malloc((strlen(response) + 3) * 2 + 1);
2826 base64_encode(response64, response, strlen(response));
2829 log_print("IMAP> %s\n", response64);
2830 sock_puts(SESSION(session)->sock, response64);
2831 ok = imap_cmd_ok(session, NULL);
2832 if (ok != IMAP_SUCCESS)
2833 log_warning(_("IMAP4 authentication failed.\n"));
2838 static gint imap_cmd_login(IMAPSession *session,
2839 const gchar *user, const gchar *pass)
2841 gchar *user_, *pass_;
2844 QUOTE_IF_REQUIRED(user_, user);
2845 QUOTE_IF_REQUIRED(pass_, pass);
2846 imap_gen_send(session, "LOGIN %s %s", user_, pass_);
2848 ok = imap_cmd_ok(session, NULL);
2849 if (ok != IMAP_SUCCESS)
2850 log_warning(_("IMAP4 login failed.\n"));
2855 static gint imap_cmd_logout(IMAPSession *session)
2857 imap_gen_send(session, "LOGOUT");
2858 return imap_cmd_ok(session, NULL);
2861 static gint imap_cmd_noop(IMAPSession *session)
2863 imap_gen_send(session, "NOOP");
2864 return imap_cmd_ok(session, NULL);
2868 static gint imap_cmd_starttls(IMAPSession *session)
2870 imap_gen_send(session, "STARTTLS");
2871 return imap_cmd_ok(session, NULL);
2875 #define THROW(err) { ok = err; goto catch; }
2877 static gint imap_cmd_namespace(IMAPSession *session, gchar **ns_str)
2883 argbuf = g_ptr_array_new();
2885 imap_gen_send(session, "NAMESPACE");
2886 if ((ok = imap_cmd_ok(session, argbuf)) != IMAP_SUCCESS) THROW(ok);
2888 str = search_array_str(argbuf, "NAMESPACE");
2889 if (!str) THROW(IMAP_ERROR);
2891 *ns_str = g_strdup(str);
2894 ptr_array_free_strings(argbuf);
2895 g_ptr_array_free(argbuf, TRUE);
2902 static gint imap_cmd_list(IMAPSession *session, const gchar *ref,
2903 const gchar *mailbox, GPtrArray *argbuf)
2905 gchar *ref_, *mailbox_;
2907 if (!ref) ref = "\"\"";
2908 if (!mailbox) mailbox = "\"\"";
2910 QUOTE_IF_REQUIRED(ref_, ref);
2911 QUOTE_IF_REQUIRED(mailbox_, mailbox);
2912 imap_gen_send(session, "LIST %s %s", ref_, mailbox_);
2914 return imap_cmd_ok(session, argbuf);
2917 #define THROW goto catch
2919 static gint imap_cmd_do_select(IMAPSession *session, const gchar *folder,
2921 gint *exists, gint *recent, gint *unseen,
2922 guint32 *uid_validity)
2929 unsigned int uid_validity_;
2931 *exists = *recent = *unseen = *uid_validity = 0;
2932 argbuf = g_ptr_array_new();
2935 select_cmd = "EXAMINE";
2937 select_cmd = "SELECT";
2939 QUOTE_IF_REQUIRED(folder_, folder);
2940 imap_gen_send(session, "%s %s", select_cmd, folder_);
2942 if ((ok = imap_cmd_ok(session, argbuf)) != IMAP_SUCCESS) THROW;
2944 resp_str = search_array_contain_str(argbuf, "EXISTS");
2946 if (sscanf(resp_str,"%d EXISTS", exists) != 1) {
2947 g_warning("imap_cmd_select(): invalid EXISTS line.\n");
2952 resp_str = search_array_contain_str(argbuf, "RECENT");
2954 if (sscanf(resp_str, "%d RECENT", recent) != 1) {
2955 g_warning("imap_cmd_select(): invalid RECENT line.\n");
2960 resp_str = search_array_contain_str(argbuf, "UIDVALIDITY");
2962 if (sscanf(resp_str, "OK [UIDVALIDITY %u] ", &uid_validity_)
2964 g_warning("imap_cmd_select(): invalid UIDVALIDITY line.\n");
2967 *uid_validity = uid_validity_;
2970 resp_str = search_array_contain_str(argbuf, "UNSEEN");
2972 if (sscanf(resp_str, "OK [UNSEEN %d] ", unseen) != 1) {
2973 g_warning("imap_cmd_select(): invalid UNSEEN line.\n");
2979 ptr_array_free_strings(argbuf);
2980 g_ptr_array_free(argbuf, TRUE);
2985 static gint imap_cmd_select(IMAPSession *session, const gchar *folder,
2986 gint *exists, gint *recent, gint *unseen,
2987 guint32 *uid_validity)
2989 return imap_cmd_do_select(session, folder, FALSE,
2990 exists, recent, unseen, uid_validity);
2993 static gint imap_cmd_examine(IMAPSession *session, const gchar *folder,
2994 gint *exists, gint *recent, gint *unseen,
2995 guint32 *uid_validity)
2997 return imap_cmd_do_select(session, folder, TRUE,
2998 exists, recent, unseen, uid_validity);
3003 static gint imap_cmd_create(IMAPSession *session, const gchar *folder)
3007 QUOTE_IF_REQUIRED(folder_, folder);
3008 imap_gen_send(session, "CREATE %s", folder_);
3010 return imap_cmd_ok(session, NULL);
3013 static gint imap_cmd_rename(IMAPSession *session, const gchar *old_folder,
3014 const gchar *new_folder)
3016 gchar *old_folder_, *new_folder_;
3018 QUOTE_IF_REQUIRED(old_folder_, old_folder);
3019 QUOTE_IF_REQUIRED(new_folder_, new_folder);
3020 imap_gen_send(session, "RENAME %s %s", old_folder_, new_folder_);
3022 return imap_cmd_ok(session, NULL);
3025 static gint imap_cmd_delete(IMAPSession *session, const gchar *folder)
3029 QUOTE_IF_REQUIRED(folder_, folder);
3030 imap_gen_send(session, "DELETE %s", folder_);
3032 return imap_cmd_ok(session, NULL);
3035 static gint imap_cmd_search(IMAPSession *session, const gchar *criteria,
3042 g_return_val_if_fail(criteria != NULL, IMAP_ERROR);
3043 g_return_val_if_fail(list != NULL, IMAP_ERROR);
3047 argbuf = g_ptr_array_new();
3048 imap_gen_send(session, "UID SEARCH %s", criteria);
3050 ok = imap_cmd_ok(session, argbuf);
3051 if (ok != IMAP_SUCCESS) {
3052 ptr_array_free_strings(argbuf);
3053 g_ptr_array_free(argbuf, TRUE);
3057 if ((uidlist = search_array_str(argbuf, "SEARCH ")) != NULL) {
3058 gchar **strlist, **p;
3060 strlist = g_strsplit(uidlist + 7, " ", 0);
3061 for (p = strlist; *p != NULL; ++p) {
3064 if (sscanf(*p, "%u", &msgnum) == 1)
3065 *list = g_slist_append(*list, GINT_TO_POINTER(msgnum));
3067 g_strfreev(strlist);
3069 ptr_array_free_strings(argbuf);
3070 g_ptr_array_free(argbuf, TRUE);
3072 return IMAP_SUCCESS;
3075 typedef struct _fetch_data {
3076 IMAPSession *session;
3078 const gchar *filename;
3082 static void *imap_cmd_fetch_thread(void *data)
3084 fetch_data *stuff = (fetch_data *)data;
3085 IMAPSession *session = stuff->session;
3086 guint32 uid = stuff->uid;
3087 const gchar *filename = stuff->filename;
3095 if (filename == NULL) {
3097 return GINT_TO_POINTER(IMAP_ERROR);
3100 imap_gen_send(session, "UID FETCH %d BODY.PEEK[]", uid);
3102 while ((ok = imap_gen_recv_block(session, &buf)) == IMAP_SUCCESS) {
3103 if (buf[0] != '*' || buf[1] != ' ') {
3106 return GINT_TO_POINTER(IMAP_ERROR);
3108 if (strstr(buf, "FETCH") != NULL) break;
3111 if (ok != IMAP_SUCCESS) {
3114 return GINT_TO_POINTER(ok);
3117 #define RETURN_ERROR_IF_FAIL(cond) \
3120 stuff->done = TRUE; \
3121 return GINT_TO_POINTER(IMAP_ERROR); \
3124 cur_pos = strchr(buf, '{');
3125 RETURN_ERROR_IF_FAIL(cur_pos != NULL);
3126 cur_pos = strchr_cpy(cur_pos + 1, '}', size_str, sizeof(size_str));
3127 RETURN_ERROR_IF_FAIL(cur_pos != NULL);
3128 size_num = atol(size_str);
3129 RETURN_ERROR_IF_FAIL(size_num >= 0);
3131 RETURN_ERROR_IF_FAIL(*cur_pos == '\0');
3133 #undef RETURN_ERROR_IF_FAIL
3137 if (recv_bytes_write_to_file(SESSION(session)->sock,
3138 size_num, filename) != 0) {
3140 return GINT_TO_POINTER(IMAP_ERROR);
3142 if (imap_gen_recv_block(session, &buf) != IMAP_SUCCESS) {
3145 return GINT_TO_POINTER(IMAP_ERROR);
3148 if (buf[0] == '\0' || buf[strlen(buf) - 1] != ')') {
3151 return GINT_TO_POINTER(IMAP_ERROR);
3155 ok = imap_cmd_ok_block(session, NULL);
3158 return GINT_TO_POINTER(ok);
3161 static gint imap_cmd_fetch(IMAPSession *session, guint32 uid,
3162 const gchar *filename)
3164 fetch_data *data = g_new0(fetch_data, 1);
3171 data->session = session;
3173 data->filename = filename;
3174 #if (defined USE_PTHREAD && defined __GLIBC__ && (__GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 3)))
3175 if (pthread_create(&pt, PTHREAD_CREATE_JOINABLE,
3176 imap_cmd_fetch_thread, data) != 0) {
3177 result = GPOINTER_TO_INT(imap_cmd_fetch_thread(data));
3181 debug_print("waiting for imap_cmd_fetch_thread...\n");
3182 while(!data->done) {
3183 /* don't let the interface freeze while waiting */
3186 debug_print("imap_cmd_fetch_thread done\n");
3188 /* get the thread's return value and clean its resources */
3189 pthread_join(pt, &tmp);
3190 result = GPOINTER_TO_INT(tmp);
3192 result = GPOINTER_TO_INT(imap_cmd_fetch_thread(data));
3198 static gint imap_cmd_append(IMAPSession *session, const gchar *destfolder,
3199 const gchar *file, IMAPFlags flags,
3206 unsigned int new_uid_;
3208 gchar buf[BUFFSIZE];
3213 g_return_val_if_fail(file != NULL, IMAP_ERROR);
3215 size = get_file_size_as_crlf(file);
3216 if ((fp = fopen(file, "rb")) == NULL) {
3217 FILE_OP_ERROR(file, "fopen");
3220 QUOTE_IF_REQUIRED(destfolder_, destfolder);
3221 flag_str = imap_get_flag_str(flags);
3222 imap_gen_send(session, "APPEND %s (%s) {%d}",
3223 destfolder_, flag_str, size);
3226 ok = imap_gen_recv(session, &ret);
3227 if (ok != IMAP_SUCCESS || ret[0] != '+' || ret[1] != ' ') {
3228 log_warning(_("can't append %s to %s\n"), file, destfolder_);
3235 log_print("IMAP4> %s\n", "(sending file...)");
3237 while (fgets(buf, sizeof(buf), fp) != NULL) {
3239 if (sock_puts(SESSION(session)->sock, buf) < 0) {
3246 FILE_OP_ERROR(file, "fgets");
3251 sock_puts(SESSION(session)->sock, "");
3255 if (new_uid != NULL)
3258 if (new_uid != NULL && session->uidplus) {
3259 argbuf = g_ptr_array_new();
3261 ok = imap_cmd_ok(session, argbuf);
3262 if ((ok == IMAP_SUCCESS) && (argbuf->len > 0)) {
3263 resp_str = g_ptr_array_index(argbuf, argbuf->len - 1);
3265 sscanf(resp_str, "%*u OK [APPENDUID %*u %u]",
3267 *new_uid = new_uid_;
3271 ptr_array_free_strings(argbuf);
3272 g_ptr_array_free(argbuf, TRUE);
3274 ok = imap_cmd_ok(session, NULL);
3276 if (ok != IMAP_SUCCESS)
3277 log_warning(_("can't append message to %s\n"),
3283 static MsgNumberList *imapset_to_numlist(IMAPSet imapset)
3285 gchar **ranges, **range;
3286 unsigned int low, high;
3287 MsgNumberList *uids = NULL;
3289 ranges = g_strsplit(imapset, ",", 0);
3290 for (range = ranges; *range != NULL; range++) {
3291 if (sscanf(*range, "%u:%u", &low, &high) == 1)
3292 uids = g_slist_prepend(uids, GINT_TO_POINTER(low));
3295 for (i = low; i <= high; i++)
3296 uids = g_slist_prepend(uids, GINT_TO_POINTER(i));
3299 uids = g_slist_reverse(uids);
3305 static gint imap_cmd_copy(IMAPSession *session, const gchar *seq_set,
3306 const gchar *destfolder, GRelation *uid_mapping)
3311 g_return_val_if_fail(session != NULL, IMAP_ERROR);
3312 g_return_val_if_fail(seq_set != NULL, IMAP_ERROR);
3313 g_return_val_if_fail(destfolder != NULL, IMAP_ERROR);
3315 QUOTE_IF_REQUIRED(destfolder_, destfolder);
3316 imap_gen_send(session, "UID COPY %s %s", seq_set, destfolder_);
3318 if (uid_mapping != NULL && session->uidplus) {
3320 gchar *resp_str = NULL, *olduids_str, *newuids_str;
3321 MsgNumberList *olduids, *old_cur, *newuids, *new_cur;
3323 reply = g_ptr_array_new();
3324 ok = imap_cmd_ok(session, reply);
3325 if ((ok == IMAP_SUCCESS) && (reply->len > 0)) {
3326 resp_str = g_ptr_array_index(reply, reply->len - 1);
3328 olduids_str = g_new0(gchar, strlen(resp_str));
3329 newuids_str = g_new0(gchar, strlen(resp_str));
3330 if (sscanf(resp_str, "%*s OK [COPYUID %*u %[0-9,:] %[0-9,:]]",
3331 olduids_str, newuids_str) == 2) {
3332 olduids = imapset_to_numlist(olduids_str);
3333 newuids = imapset_to_numlist(newuids_str);
3337 while(old_cur != NULL && new_cur != NULL) {
3338 g_relation_insert(uid_mapping,
3339 GPOINTER_TO_INT(old_cur->data),
3340 GPOINTER_TO_INT(new_cur->data));
3341 old_cur = g_slist_next(old_cur);
3342 new_cur = g_slist_next(new_cur);
3345 g_slist_free(olduids);
3346 g_slist_free(newuids);
3348 g_free(olduids_str);
3349 g_free(newuids_str);
3352 ptr_array_free_strings(reply);
3353 g_ptr_array_free(reply, TRUE);
3355 ok = imap_cmd_ok(session, NULL);
3357 if (ok != IMAP_SUCCESS)
3358 log_warning(_("can't copy %s to %s\n"), seq_set, destfolder_);
3363 gint imap_cmd_envelope(IMAPSession *session, IMAPSet set)
3365 static GString *header_fields = NULL;
3367 if (header_fields == NULL) {
3368 const HeaderEntry *headers, *elem;
3370 headers = procheader_get_headernames(FALSE);
3371 header_fields = g_string_new("");
3373 for (elem = headers; elem->name != NULL; ++elem) {
3374 gint namelen = strlen(elem->name);
3376 /* Header fields ending with space are not rfc822 headers */
3377 if (elem->name[namelen - 1] == ' ')
3380 /* strip : at the of header field */
3381 if(elem->name[namelen - 1] == ':')
3387 g_string_append_printf(header_fields, "%s%.*s",
3388 header_fields->str[0] != '\0' ? " " : "",
3389 namelen, elem->name);
3394 (session, "UID FETCH %s (UID FLAGS RFC822.SIZE RFC822.HEADER)",
3397 return IMAP_SUCCESS;
3400 static gint imap_cmd_store(IMAPSession *session, IMAPSet seq_set,
3405 imap_gen_send(session, "UID STORE %s %s", seq_set, sub_cmd);
3407 if ((ok = imap_cmd_ok(session, NULL)) != IMAP_SUCCESS) {
3408 log_warning(_("error while imap command: STORE %s %s\n"),
3413 return IMAP_SUCCESS;
3416 static gint imap_cmd_expunge(IMAPSession *session, IMAPSet seq_set)
3420 if (seq_set && session->uidplus)
3421 imap_gen_send(session, "UID EXPUNGE %s", seq_set);
3423 imap_gen_send(session, "EXPUNGE");
3424 if ((ok = imap_cmd_ok(session, NULL)) != IMAP_SUCCESS) {
3425 log_warning(_("error while imap command: EXPUNGE\n"));
3429 return IMAP_SUCCESS;
3432 static gint imap_cmd_close(IMAPSession *session)
3436 imap_gen_send(session, "CLOSE");
3437 if ((ok = imap_cmd_ok(session, NULL)) != IMAP_SUCCESS)
3438 log_warning(_("error while imap command: CLOSE\n"));
3443 static gint imap_cmd_ok_with_block(IMAPSession *session, GPtrArray *argbuf, gboolean block)
3445 gint ok = IMAP_SUCCESS;
3450 while ((ok = imap_gen_recv_with_block(session, &buf, block))
3452 /* make sure data is long enough for any substring of buf */
3453 data = alloca(strlen(buf) + 1);
3455 /* untagged line read */
3456 if (buf[0] == '*' && buf[1] == ' ') {
3459 g_ptr_array_add(argbuf, g_strdup(buf + 2));
3461 if (sscanf(buf + 2, "%d %s", &num, data) >= 2) {
3462 if (!strcmp(data, "EXISTS")) {
3463 session->exists = num;
3464 session->folder_content_changed = TRUE;
3467 if(!strcmp(data, "EXPUNGE")) {
3469 session->folder_content_changed = TRUE;
3472 /* tagged line with correct tag and OK response found */
3473 } else if ((sscanf(buf, "%d %s", &cmd_num, data) >= 2) &&
3474 (cmd_num == session->cmd_count) &&
3475 !strcmp(data, "OK")) {
3477 g_ptr_array_add(argbuf, g_strdup(buf));
3479 /* everything else */
3490 static gint imap_cmd_ok(IMAPSession *session, GPtrArray *argbuf)
3492 return imap_cmd_ok_with_block(session, argbuf, FALSE);
3494 static gint imap_cmd_ok_block(IMAPSession *session, GPtrArray *argbuf)
3496 return imap_cmd_ok_with_block(session, argbuf, TRUE);
3498 static void imap_gen_send(IMAPSession *session, const gchar *format, ...)
3505 va_start(args, format);
3506 tmp = g_strdup_vprintf(format, args);
3509 session->cmd_count++;
3511 buf = g_strdup_printf("%d %s\r\n", session->cmd_count, tmp);
3512 if (!g_ascii_strncasecmp(tmp, "LOGIN ", 6) && (p = strchr(tmp + 6, ' '))) {
3514 log_print("IMAP4> %d %s ********\n", session->cmd_count, tmp);
3516 log_print("IMAP4> %d %s\n", session->cmd_count, tmp);
3518 sock_write_all(SESSION(session)->sock, buf, strlen(buf));
3523 static gint imap_gen_recv_with_block(IMAPSession *session, gchar **ret, gboolean block)
3526 if ((*ret = imap_getline(SESSION(session)->sock)) == NULL)
3529 if ((*ret = sock_getline(SESSION(session)->sock)) == NULL)
3534 log_print("IMAP4< %s\n", *ret);
3536 session_set_access_time(SESSION(session));
3538 return IMAP_SUCCESS;
3541 static gint imap_gen_recv_block(IMAPSession *session, gchar **ret)
3543 return imap_gen_recv_with_block(session, ret, TRUE);
3546 static gint imap_gen_recv(IMAPSession *session, gchar **ret)
3548 return imap_gen_recv_with_block(session, ret, FALSE);
3550 /* misc utility functions */
3552 static gchar *strchr_cpy(const gchar *src, gchar ch, gchar *dest, gint len)
3557 tmp = strchr(src, ch);
3561 memcpy(dest, src, MIN(tmp - src, len - 1));
3562 dest[MIN(tmp - src, len - 1)] = '\0';
3567 static gchar *get_quoted(const gchar *src, gchar ch, gchar *dest, gint len)
3569 const gchar *p = src;
3572 g_return_val_if_fail(*p == ch, NULL);
3577 while (*p != '\0' && *p != ch) {
3579 if (*p == '\\' && *(p + 1) != '\0')
3588 return (gchar *)(*p == ch ? p + 1 : p);
3591 static gchar *search_array_contain_str(GPtrArray *array, const gchar *str)
3595 for (i = 0; i < array->len; i++) {
3598 tmp = g_ptr_array_index(array, i);
3599 if (strstr(tmp, str) != NULL)
3606 static gchar *search_array_str(GPtrArray *array, const gchar *str)
3613 for (i = 0; i < array->len; i++) {
3616 tmp = g_ptr_array_index(array, i);
3617 if (!strncmp(tmp, str, len))
3624 static void imap_path_separator_subst(gchar *str, gchar separator)
3627 gboolean in_escape = FALSE;
3629 if (!separator || separator == '/') return;
3631 for (p = str; *p != '\0'; p++) {
3632 if (*p == '/' && !in_escape)
3634 else if (*p == '&' && *(p + 1) != '-' && !in_escape)
3636 else if (*p == '-' && in_escape)
3641 static gchar *imap_modified_utf7_to_utf8(const gchar *mutf7_str)
3643 static iconv_t cd = (iconv_t)-1;
3644 static gboolean iconv_ok = TRUE;
3647 size_t norm_utf7_len;
3649 gchar *to_str, *to_p;
3651 gboolean in_escape = FALSE;
3653 if (!iconv_ok) return g_strdup(mutf7_str);
3655 if (cd == (iconv_t)-1) {
3656 cd = iconv_open(CS_INTERNAL, CS_UTF_7);
3657 if (cd == (iconv_t)-1) {
3658 g_warning("iconv cannot convert UTF-7 to %s\n",
3661 return g_strdup(mutf7_str);
3665 /* modified UTF-7 to normal UTF-7 conversion */
3666 norm_utf7 = g_string_new(NULL);
3668 for (p = mutf7_str; *p != '\0'; p++) {
3669 /* replace: '&' -> '+',
3671 escaped ',' -> '/' */
3672 if (!in_escape && *p == '&') {
3673 if (*(p + 1) != '-') {
3674 g_string_append_c(norm_utf7, '+');
3677 g_string_append_c(norm_utf7, '&');
3680 } else if (in_escape && *p == ',') {
3681 g_string_append_c(norm_utf7, '/');
3682 } else if (in_escape && *p == '-') {
3683 g_string_append_c(norm_utf7, '-');
3686 g_string_append_c(norm_utf7, *p);
3690 norm_utf7_p = norm_utf7->str;
3691 norm_utf7_len = norm_utf7->len;
3692 to_len = strlen(mutf7_str) * 5;
3693 to_p = to_str = g_malloc(to_len + 1);
3695 if (iconv(cd, (ICONV_CONST gchar **)&norm_utf7_p, &norm_utf7_len,
3696 &to_p, &to_len) == -1) {
3697 g_warning(_("iconv cannot convert UTF-7 to %s\n"),
3698 conv_get_locale_charset_str());
3699 g_string_free(norm_utf7, TRUE);
3701 return g_strdup(mutf7_str);
3704 /* second iconv() call for flushing */
3705 iconv(cd, NULL, NULL, &to_p, &to_len);
3706 g_string_free(norm_utf7, TRUE);
3712 static gchar *imap_utf8_to_modified_utf7(const gchar *from)
3714 static iconv_t cd = (iconv_t)-1;
3715 static gboolean iconv_ok = TRUE;
3716 gchar *norm_utf7, *norm_utf7_p;
3717 size_t from_len, norm_utf7_len;
3719 gchar *from_tmp, *to, *p;
3720 gboolean in_escape = FALSE;
3722 if (!iconv_ok) return g_strdup(from);
3724 if (cd == (iconv_t)-1) {
3725 cd = iconv_open(CS_UTF_7, CS_INTERNAL);
3726 if (cd == (iconv_t)-1) {
3727 g_warning(_("iconv cannot convert %s to UTF-7\n"),
3730 return g_strdup(from);
3734 /* UTF-8 to normal UTF-7 conversion */
3735 Xstrdup_a(from_tmp, from, return g_strdup(from));
3736 from_len = strlen(from);
3737 norm_utf7_len = from_len * 5;
3738 Xalloca(norm_utf7, norm_utf7_len + 1, return g_strdup(from));
3739 norm_utf7_p = norm_utf7;
3741 #define IS_PRINT(ch) (isprint(ch) && IS_ASCII(ch))
3743 while (from_len > 0) {
3744 if (*from_tmp == '+') {
3745 *norm_utf7_p++ = '+';
3746 *norm_utf7_p++ = '-';
3750 } else if (IS_PRINT(*(guchar *)from_tmp)) {
3751 /* printable ascii char */
3752 *norm_utf7_p = *from_tmp;
3758 size_t conv_len = 0;
3760 /* unprintable char: convert to UTF-7 */
3762 while (!IS_PRINT(*(guchar *)p) && conv_len < from_len) {
3763 conv_len += g_utf8_skip[*(guchar *)p];
3764 p += g_utf8_skip[*(guchar *)p];
3767 from_len -= conv_len;
3768 if (iconv(cd, (ICONV_CONST gchar **)&from_tmp,
3770 &norm_utf7_p, &norm_utf7_len) == -1) {
3771 g_warning(_("iconv cannot convert UTF-8 to UTF-7\n"));
3772 return g_strdup(from);
3775 /* second iconv() call for flushing */
3776 iconv(cd, NULL, NULL, &norm_utf7_p, &norm_utf7_len);
3782 *norm_utf7_p = '\0';
3783 to_str = g_string_new(NULL);
3784 for (p = norm_utf7; p < norm_utf7_p; p++) {
3785 /* replace: '&' -> "&-",
3788 BASE64 '/' -> ',' */
3789 if (!in_escape && *p == '&') {
3790 g_string_append(to_str, "&-");
3791 } else if (!in_escape && *p == '+') {
3792 if (*(p + 1) == '-') {
3793 g_string_append_c(to_str, '+');
3796 g_string_append_c(to_str, '&');
3799 } else if (in_escape && *p == '/') {
3800 g_string_append_c(to_str, ',');
3801 } else if (in_escape && *p == '-') {
3802 g_string_append_c(to_str, '-');
3805 g_string_append_c(to_str, *p);
3811 g_string_append_c(to_str, '-');
3815 g_string_free(to_str, FALSE);
3820 static GSList *imap_get_seq_set_from_numlist(MsgNumberList *numlist)
3823 GSList *sorted_list, *cur;
3824 guint first, last, next;
3826 GSList *ret_list = NULL;
3828 if (numlist == NULL)
3831 str = g_string_sized_new(256);
3833 sorted_list = g_slist_copy(numlist);
3834 sorted_list = g_slist_sort(sorted_list, g_int_compare);
3836 first = GPOINTER_TO_INT(sorted_list->data);
3838 for (cur = sorted_list; cur != NULL; cur = g_slist_next(cur)) {
3839 last = GPOINTER_TO_INT(cur->data);
3841 next = GPOINTER_TO_INT(cur->next->data);
3845 if (last + 1 != next || next == 0) {
3847 g_string_append_c(str, ',');
3849 g_string_append_printf(str, "%u", first);
3851 g_string_append_printf(str, "%u:%u", first, last);
3855 if (str->len > IMAP_CMD_LIMIT) {
3856 ret_str = g_strdup(str->str);
3857 ret_list = g_slist_append(ret_list, ret_str);
3858 g_string_truncate(str, 0);
3864 ret_str = g_strdup(str->str);
3865 ret_list = g_slist_append(ret_list, ret_str);
3868 g_slist_free(sorted_list);
3869 g_string_free(str, TRUE);
3874 static GSList *imap_get_seq_set_from_msglist(MsgInfoList *msglist)
3876 MsgNumberList *numlist = NULL;
3880 for (cur = msglist; cur != NULL; cur = g_slist_next(cur)) {
3881 MsgInfo *msginfo = (MsgInfo *) cur->data;
3883 numlist = g_slist_append(numlist, GINT_TO_POINTER(msginfo->msgnum));
3885 seq_list = imap_get_seq_set_from_numlist(numlist);
3886 g_slist_free(numlist);
3891 static void imap_seq_set_free(GSList *seq_list)
3893 slist_free_strings(seq_list);
3894 g_slist_free(seq_list);
3898 static gboolean imap_rename_folder_func(GNode *node, gpointer data)
3900 FolderItem *item = node->data;
3901 gchar **paths = data;
3902 const gchar *oldpath = paths[0];
3903 const gchar *newpath = paths[1];
3905 gchar *new_itempath;
3908 oldpathlen = strlen(oldpath);
3909 if (strncmp(oldpath, item->path, oldpathlen) != 0) {
3910 g_warning("path doesn't match: %s, %s\n", oldpath, item->path);
3914 base = item->path + oldpathlen;
3915 while (*base == G_DIR_SEPARATOR) base++;
3917 new_itempath = g_strdup(newpath);
3919 new_itempath = g_strconcat(newpath, G_DIR_SEPARATOR_S, base,
3922 item->path = new_itempath;
3927 static gint get_list_of_uids(Folder *folder, IMAPFolderItem *item, GSList **msgnum_list)
3929 gint ok, nummsgs = 0, lastuid_old;
3930 IMAPSession *session;
3931 GSList *uidlist, *elem;
3934 session = imap_session_get(folder);
3935 g_return_val_if_fail(session != NULL, -1);
3937 ok = imap_select(session, IMAP_FOLDER(folder), item->item.path,
3938 NULL, NULL, NULL, NULL);
3939 if (ok != IMAP_SUCCESS)
3942 cmd_buf = g_strdup_printf("UID %d:*", item->lastuid + 1);
3943 ok = imap_cmd_search(session, cmd_buf, &uidlist);
3946 if (ok == IMAP_SOCKET) {
3947 session_destroy((Session *)session);
3948 ((RemoteFolder *)folder)->session = NULL;
3952 if (ok != IMAP_SUCCESS) {
3956 argbuf = g_ptr_array_new();
3958 cmd_buf = g_strdup_printf("UID FETCH %d:* (UID)", item->lastuid + 1);
3959 imap_gen_send(session, cmd_buf);
3961 ok = imap_cmd_ok(session, argbuf);
3962 if (ok != IMAP_SUCCESS) {
3963 ptr_array_free_strings(argbuf);
3964 g_ptr_array_free(argbuf, TRUE);
3968 for(i = 0; i < argbuf->len; i++) {
3971 if((ret = sscanf(g_ptr_array_index(argbuf, i),
3972 "%*d FETCH (UID %d)", &msgnum)) == 1)
3973 uidlist = g_slist_prepend(uidlist, GINT_TO_POINTER(msgnum));
3975 ptr_array_free_strings(argbuf);
3976 g_ptr_array_free(argbuf, TRUE);
3979 lastuid_old = item->lastuid;
3980 *msgnum_list = g_slist_copy(item->uid_list);
3981 nummsgs = g_slist_length(*msgnum_list);
3982 debug_print("Got %d uids from cache\n", g_slist_length(item->uid_list));
3984 for (elem = uidlist; elem != NULL; elem = g_slist_next(elem)) {
3987 msgnum = GPOINTER_TO_INT(elem->data);
3988 if (msgnum > lastuid_old) {
3989 *msgnum_list = g_slist_prepend(*msgnum_list, GINT_TO_POINTER(msgnum));
3990 item->uid_list = g_slist_prepend(item->uid_list, GINT_TO_POINTER(msgnum));
3993 if(msgnum > item->lastuid)
3994 item->lastuid = msgnum;
3997 g_slist_free(uidlist);
4002 gint imap_get_num_list(Folder *folder, FolderItem *_item, GSList **msgnum_list, gboolean *old_uids_valid)
4004 IMAPFolderItem *item = (IMAPFolderItem *)_item;
4005 IMAPSession *session;
4006 gint ok, nummsgs = 0, exists, recent, uid_val, uid_next, unseen;
4007 GSList *uidlist = NULL;
4009 gboolean selected_folder;
4011 g_return_val_if_fail(folder != NULL, -1);
4012 g_return_val_if_fail(item != NULL, -1);
4013 g_return_val_if_fail(item->item.path != NULL, -1);
4014 g_return_val_if_fail(FOLDER_CLASS(folder) == &imap_class, -1);
4015 g_return_val_if_fail(folder->account != NULL, -1);
4017 session = imap_session_get(folder);
4018 g_return_val_if_fail(session != NULL, -1);
4020 selected_folder = (session->mbox != NULL) &&
4021 (!strcmp(session->mbox, item->item.path));
4022 if (selected_folder) {
4023 ok = imap_cmd_noop(session);
4024 if (ok != IMAP_SUCCESS)
4026 exists = session->exists;
4028 *old_uids_valid = TRUE;
4030 ok = imap_status(session, IMAP_FOLDER(folder), item->item.path,
4031 &exists, &recent, &uid_next, &uid_val, &unseen);
4032 if (ok != IMAP_SUCCESS)
4035 if(item->item.mtime == uid_val)
4036 *old_uids_valid = TRUE;
4038 *old_uids_valid = FALSE;
4040 debug_print("Freeing imap uid cache\n");
4042 g_slist_free(item->uid_list);
4043 item->uid_list = NULL;
4045 item->item.mtime = uid_val;
4047 imap_delete_all_cached_messages((FolderItem *)item);
4051 if (!selected_folder)
4052 item->uid_next = uid_next;
4054 /* If old uid_next matches new uid_next we can be sure no message
4055 was added to the folder */
4056 if (( selected_folder && !session->folder_content_changed) ||
4057 (!selected_folder && uid_next == item->uid_next)) {
4058 nummsgs = g_slist_length(item->uid_list);
4060 /* If number of messages is still the same we
4061 know our caches message numbers are still valid,
4062 otherwise if the number of messages has decrease
4063 we discard our cache to start a new scan to find
4064 out which numbers have been removed */
4065 if (exists == nummsgs) {
4066 *msgnum_list = g_slist_copy(item->uid_list);
4068 } else if (exists < nummsgs) {
4069 debug_print("Freeing imap uid cache");
4071 g_slist_free(item->uid_list);
4072 item->uid_list = NULL;
4077 *msgnum_list = NULL;
4081 nummsgs = get_list_of_uids(folder, item, &uidlist);
4083 if (nummsgs != exists) {
4084 /* Cache contains more messages then folder, we have cached
4085 an old UID of a message that was removed and new messages
4086 have been added too, otherwise the uid_next check would
4088 debug_print("Freeing imap uid cache");
4090 g_slist_free(item->uid_list);
4091 item->uid_list = NULL;
4093 g_slist_free(*msgnum_list);
4095 nummsgs = get_list_of_uids(folder, item, &uidlist);
4098 *msgnum_list = uidlist;
4100 dir = folder_item_get_path((FolderItem *)item);
4101 debug_print("removing old messages from %s\n", dir);
4102 remove_numbered_files_not_in_list(dir, *msgnum_list);
4108 static MsgInfo *imap_parse_msg(const gchar *file, FolderItem *item)
4113 flags.perm_flags = MSG_NEW|MSG_UNREAD;
4114 flags.tmp_flags = 0;
4116 g_return_val_if_fail(item != NULL, NULL);
4117 g_return_val_if_fail(file != NULL, NULL);
4119 if (item->stype == F_QUEUE) {
4120 MSG_SET_TMP_FLAGS(flags, MSG_QUEUED);
4121 } else if (item->stype == F_DRAFT) {
4122 MSG_SET_TMP_FLAGS(flags, MSG_DRAFT);
4125 msginfo = procheader_parse_file(file, flags, FALSE, FALSE);
4126 if (!msginfo) return NULL;
4128 msginfo->folder = item;
4133 GSList *imap_get_msginfos(Folder *folder, FolderItem *item, GSList *msgnum_list)
4135 IMAPSession *session;
4136 MsgInfoList *ret = NULL;
4139 g_return_val_if_fail(folder != NULL, NULL);
4140 g_return_val_if_fail(item != NULL, NULL);
4141 g_return_val_if_fail(msgnum_list != NULL, NULL);
4143 session = imap_session_get(folder);
4144 g_return_val_if_fail(session != NULL, NULL);
4146 ok = imap_select(session, IMAP_FOLDER(folder), item->path,
4147 NULL, NULL, NULL, NULL);
4148 if (ok != IMAP_SUCCESS)
4151 if (!(item->stype == F_QUEUE || item->stype == F_DRAFT)) {
4152 ret = g_slist_concat(ret,
4153 imap_get_uncached_messages(
4154 session, item, msgnum_list));
4156 MsgNumberList *sorted_list, *elem;
4157 gint startnum, lastnum;
4159 sorted_list = g_slist_sort(g_slist_copy(msgnum_list), g_int_compare);
4161 startnum = lastnum = GPOINTER_TO_INT(sorted_list->data);
4163 for (elem = sorted_list;; elem = g_slist_next(elem)) {
4167 num = GPOINTER_TO_INT(elem->data);
4169 if (num > lastnum + 1 || elem == NULL) {
4171 for (i = startnum; i <= lastnum; ++i) {
4174 file = imap_fetch_msg(folder, item, i);
4176 MsgInfo *msginfo = imap_parse_msg(file, item);
4177 if (msginfo != NULL) {
4178 msginfo->msgnum = i;
4179 ret = g_slist_append(ret, msginfo);
4193 g_slist_free(sorted_list);
4199 MsgInfo *imap_get_msginfo(Folder *folder, FolderItem *item, gint uid)
4201 MsgInfo *msginfo = NULL;
4202 MsgInfoList *msginfolist;
4203 MsgNumberList numlist;
4205 numlist.next = NULL;
4206 numlist.data = GINT_TO_POINTER(uid);
4208 msginfolist = imap_get_msginfos(folder, item, &numlist);
4209 if (msginfolist != NULL) {
4210 msginfo = msginfolist->data;
4211 g_slist_free(msginfolist);
4217 gboolean imap_scan_required(Folder *folder, FolderItem *_item)
4219 IMAPSession *session;
4220 IMAPFolderItem *item = (IMAPFolderItem *)_item;
4221 gint ok, exists = 0, recent = 0, unseen = 0;
4222 guint32 uid_next, uid_val = 0;
4223 gboolean selected_folder;
4225 g_return_val_if_fail(folder != NULL, FALSE);
4226 g_return_val_if_fail(item != NULL, FALSE);
4227 g_return_val_if_fail(item->item.folder != NULL, FALSE);
4228 g_return_val_if_fail(FOLDER_CLASS(item->item.folder) == &imap_class, FALSE);
4230 if (item->item.path == NULL)
4233 session = imap_session_get(folder);
4234 g_return_val_if_fail(session != NULL, FALSE);
4236 selected_folder = (session->mbox != NULL) &&
4237 (!strcmp(session->mbox, item->item.path));
4238 if (selected_folder) {
4239 ok = imap_cmd_noop(session);
4240 if (ok != IMAP_SUCCESS)
4243 if (session->folder_content_changed)
4246 ok = imap_status(session, IMAP_FOLDER(folder), item->item.path,
4247 &exists, &recent, &uid_next, &uid_val, &unseen);
4248 if (ok != IMAP_SUCCESS)
4251 if ((uid_next != item->uid_next) || (exists < item->item.total_msgs))
4258 void imap_change_flags(Folder *folder, FolderItem *item, MsgInfo *msginfo, MsgPermFlags newflags)
4260 IMAPSession *session;
4261 IMAPFlags flags_set = 0, flags_unset = 0;
4262 gint ok = IMAP_SUCCESS;
4263 MsgNumberList numlist;
4265 g_return_if_fail(folder != NULL);
4266 g_return_if_fail(folder->klass == &imap_class);
4267 g_return_if_fail(item != NULL);
4268 g_return_if_fail(item->folder == folder);
4269 g_return_if_fail(msginfo != NULL);
4270 g_return_if_fail(msginfo->folder == item);
4272 session = imap_session_get(folder);
4273 if (!session) return;
4275 if ((ok = imap_select(session, IMAP_FOLDER(folder), msginfo->folder->path,
4276 NULL, NULL, NULL, NULL)) != IMAP_SUCCESS)
4279 if (!MSG_IS_MARKED(msginfo->flags) && (newflags & MSG_MARKED))
4280 flags_set |= IMAP_FLAG_FLAGGED;
4281 if ( MSG_IS_MARKED(msginfo->flags) && !(newflags & MSG_MARKED))
4282 flags_unset |= IMAP_FLAG_FLAGGED;
4284 if (!MSG_IS_UNREAD(msginfo->flags) && (newflags & MSG_UNREAD))
4285 flags_unset |= IMAP_FLAG_SEEN;
4286 if ( MSG_IS_UNREAD(msginfo->flags) && !(newflags & MSG_UNREAD))
4287 flags_set |= IMAP_FLAG_SEEN;
4289 if (!MSG_IS_REPLIED(msginfo->flags) && (newflags & MSG_REPLIED))
4290 flags_set |= IMAP_FLAG_ANSWERED;
4291 if ( MSG_IS_REPLIED(msginfo->flags) && !(newflags & MSG_REPLIED))
4292 flags_set |= IMAP_FLAG_ANSWERED;
4294 numlist.next = NULL;
4295 numlist.data = GINT_TO_POINTER(msginfo->msgnum);
4298 ok = imap_set_message_flags(session, &numlist, flags_set, TRUE);
4299 if (ok != IMAP_SUCCESS) return;
4303 ok = imap_set_message_flags(session, &numlist, flags_unset, FALSE);
4304 if (ok != IMAP_SUCCESS) return;
4307 msginfo->flags.perm_flags = newflags;
4312 static gint compare_msginfo(gconstpointer a, gconstpointer b)
4314 return ((MsgInfo *)a)->msgnum - ((MsgInfo *)b)->msgnum;
4317 static guint gslist_find_next_num(MsgNumberList **list, guint num)
4321 g_return_val_if_fail(list != NULL, -1);
4323 for (elem = *list; elem != NULL; elem = g_slist_next(elem))
4324 if (GPOINTER_TO_INT(elem->data) >= num)
4327 return elem != NULL ? GPOINTER_TO_INT(elem->data) : (gint)-1;
4331 * NEW and DELETED flags are not syncronized
4332 * - The NEW/RECENT flags in IMAP folders can not really be directly
4333 * modified by Sylpheed
4334 * - The DELETE/DELETED flag in IMAP and Sylpheed don't have the same
4335 * meaning, in IMAP it always removes the messages from the FolderItem
4336 * in Sylpheed it can mean to move the message to trash
4338 static gint imap_get_flags(Folder *folder, FolderItem *item,
4339 MsgInfoList *msginfo_list, GRelation *msgflags)
4341 IMAPSession *session;
4342 GSList *sorted_list;
4344 GSList *new = NULL, *p_new;
4345 GSList *deleted = NULL, *p_deleted;
4347 GSList *unseen = NULL, *answered = NULL, *flagged = NULL;
4348 GSList *p_unseen, *p_answered, *p_flagged;
4350 GSList *seq_list, *cur;
4351 gboolean reverse_seen = FALSE;
4354 gint exists_cnt, recent_cnt, unseen_cnt, uid_next;
4355 guint32 uidvalidity;
4357 g_return_val_if_fail(folder != NULL, -1);
4358 g_return_val_if_fail(item != NULL, -1);
4359 if (msginfo_list == NULL)
4362 session = imap_session_get(folder);
4363 g_return_val_if_fail(session != NULL, -1);
4365 ok = imap_select(session, IMAP_FOLDER(folder), item->path,
4366 NULL, NULL, NULL, NULL);
4367 if (ok != IMAP_SUCCESS)
4370 ok = imap_status(session, IMAP_FOLDER(folder), item->path,
4371 &exists_cnt, &recent_cnt, &uid_next, &uidvalidity, &unseen_cnt);
4373 if (unseen_cnt > exists_cnt / 2)
4374 reverse_seen = TRUE;
4376 cmd_buf = g_string_new(NULL);
4378 sorted_list = g_slist_sort(g_slist_copy(msginfo_list), compare_msginfo);
4380 seq_list = imap_get_seq_set_from_msglist(msginfo_list);
4382 for (cur = seq_list; cur != NULL; cur = g_slist_next(cur)) {
4383 IMAPSet imapset = cur->data;
4385 g_string_sprintf(cmd_buf, "RECENT UID %s", imapset);
4386 imap_cmd_search(session, cmd_buf->str, &p_new);
4387 new = g_slist_concat(new, p_new);
4389 g_string_sprintf(cmd_buf, "%sSEEN UID %s", reverse_seen ? "" : "UN", imapset);
4390 imap_cmd_search(session, cmd_buf->str, &p_unseen);
4391 unseen = g_slist_concat(unseen, p_unseen);
4393 g_string_sprintf(cmd_buf, "ANSWERED UID %s", imapset);
4394 imap_cmd_search(session, cmd_buf->str, &p_answered);
4395 answered = g_slist_concat(answered, p_answered);
4397 g_string_sprintf(cmd_buf, "FLAGGED UID %s", imapset);
4398 imap_cmd_search(session, cmd_buf->str, &p_flagged);
4399 flagged = g_slist_concat(flagged, p_flagged);
4401 g_string_sprintf(cmd_buf, "DELETED UID %s", imapset);
4402 imap_cmd_search(session, cmd_buf->str, &p_deleted);
4403 deleted = g_slist_concat(deleted, p_deleted);
4411 p_answered = answered;
4412 p_flagged = flagged;
4414 p_deleted = deleted;
4416 for (elem = sorted_list; elem != NULL; elem = g_slist_next(elem)) {
4421 msginfo = (MsgInfo *) elem->data;
4422 flags = msginfo->flags.perm_flags;
4423 wasnew = (flags & MSG_NEW);
4424 flags &= ~((reverse_seen ? 0 : MSG_UNREAD | MSG_NEW) | MSG_REPLIED | MSG_MARKED);
4426 flags |= MSG_UNREAD | (wasnew ? MSG_NEW : 0);
4428 if (gslist_find_next_num(&p_new, msginfo->msgnum) == msginfo->msgnum)
4431 if (gslist_find_next_num(&p_unseen, msginfo->msgnum) == msginfo->msgnum) {
4432 if (!reverse_seen) {
4433 flags |= MSG_UNREAD | (wasnew ? MSG_NEW : 0);
4435 flags &= ~(MSG_UNREAD | MSG_NEW);
4438 if (gslist_find_next_num(&p_answered, msginfo->msgnum) == msginfo->msgnum)
4439 flags |= MSG_REPLIED;
4440 if (gslist_find_next_num(&p_flagged, msginfo->msgnum) == msginfo->msgnum)
4441 flags |= MSG_MARKED;
4443 if (gslist_find_next_num(&p_deleted, msginfo->msgnum) == msginfo->msgnum)
4444 MSG_SET_PERM_FLAGS(msginfo->flags, MSG_DELETED);
4446 g_relation_insert(msgflags, msginfo, GINT_TO_POINTER(flags));
4449 imap_seq_set_free(seq_list);
4450 /* g_slist_free(deleted); */
4451 g_slist_free(flagged);
4452 g_slist_free(answered);
4453 g_slist_free(unseen);
4454 /* new not freed in original patch ??? */
4455 g_slist_free(sorted_list);
4456 g_string_free(cmd_buf, TRUE);