2 * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2003 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.
49 #include "procheader.h"
50 #include "prefs_account.h"
53 #include "inputdialog.h"
56 typedef struct _IMAPFolder IMAPFolder;
57 typedef struct _IMAPSession IMAPSession;
58 typedef struct _IMAPNameSpace IMAPNameSpace;
59 typedef struct _IMAPFolderItem IMAPFolderItem;
61 #include "prefs_account.h"
63 #define IMAP_FOLDER(obj) ((IMAPFolder *)obj)
64 #define IMAP_SESSION(obj) ((IMAPSession *)obj)
70 /* list of IMAPNameSpace */
82 time_t last_access_time;
83 gboolean authenticated;
85 gboolean folder_content_changed;
95 #define IMAP_SUCCESS 0
97 #define IMAP_AUTHFAIL 3
98 #define IMAP_PROTOCOL 4
103 #define IMAPBUFSIZE 8192
104 #define IMAPCMDLIMIT 1000
108 IMAP_FLAG_SEEN = 1 << 0,
109 IMAP_FLAG_ANSWERED = 1 << 1,
110 IMAP_FLAG_FLAGGED = 1 << 2,
111 IMAP_FLAG_DELETED = 1 << 3,
112 IMAP_FLAG_DRAFT = 1 << 4
115 #define IMAP_IS_SEEN(flags) ((flags & IMAP_FLAG_SEEN) != 0)
116 #define IMAP_IS_ANSWERED(flags) ((flags & IMAP_FLAG_ANSWERED) != 0)
117 #define IMAP_IS_FLAGGED(flags) ((flags & IMAP_FLAG_FLAGGED) != 0)
118 #define IMAP_IS_DELETED(flags) ((flags & IMAP_FLAG_DELETED) != 0)
119 #define IMAP_IS_DRAFT(flags) ((flags & IMAP_FLAG_DRAFT) != 0)
122 #define IMAP4_PORT 143
124 #define IMAPS_PORT 993
127 #define QUOTE_IF_REQUIRED(out, str) \
129 if (*str != '"' && strpbrk(str, " \t(){}%*") != NULL) { \
133 len = strlen(str) + 3; \
134 Xalloca(__tmp, len, return IMAP_ERROR); \
135 g_snprintf(__tmp, len, "\"%s\"", str); \
138 Xstrdup_a(out, str, return IMAP_ERROR); \
142 typedef gchar * IMAPSet;
144 struct _IMAPFolderItem
153 static Folder *imap_folder_new(const gchar * name, const gchar * path);
154 static void imap_folder_destroy(Folder * folder);
156 static IMAPSession *imap_session_new(const PrefsAccount * account);
157 static void imap_session_authenticate(IMAPSession * session,
158 const PrefsAccount * account);
159 static void imap_session_destroy(Session * session);
161 static gchar *imap_fetch_msg(Folder * folder, FolderItem * item, gint uid);
162 static gint imap_add_msg(Folder * folder,
164 const gchar * file, gboolean remove_source);
166 static gint imap_copy_msg(Folder * folder,
167 FolderItem * dest, MsgInfo * msginfo);
169 static gint imap_remove_msg(Folder * folder, FolderItem * item, gint uid);
170 static gint imap_remove_all_msg(Folder * folder, FolderItem * item);
172 static gboolean imap_is_msg_changed(Folder * folder,
173 FolderItem * item, MsgInfo * msginfo);
175 static void imap_scan_tree(Folder * folder);
177 static gint imap_create_tree(Folder * folder);
179 static FolderItem *imap_create_folder(Folder * folder,
182 static gint imap_rename_folder(Folder * folder,
183 FolderItem * item, const gchar * name);
184 static gint imap_remove_folder(Folder * folder, FolderItem * item);
187 static void imap_folder_init (Folder *folder,
191 static FolderItem *imap_folder_item_new (Folder *folder);
192 static void imap_folder_item_destroy (Folder *folder,
195 static IMAPSession *imap_session_get (Folder *folder);
197 static gint imap_scan_tree_recursive (IMAPSession *session,
199 static GSList *imap_parse_list (IMAPFolder *folder,
200 IMAPSession *session,
201 const gchar *real_path,
204 static void imap_create_missing_folders (Folder *folder);
205 static FolderItem *imap_create_special_folder
207 SpecialFolderItemType stype,
210 static gint imap_do_copy (Folder *folder,
213 gboolean remove_source);
215 static void imap_delete_all_cached_messages (FolderItem *item);
218 static SockInfo *imap_open (const gchar *server,
222 static SockInfo *imap_open (const gchar *server,
227 static SockInfo *imap_open_tunnel(const gchar *server,
228 const gchar *tunnelcmd,
231 static SockInfo *imap_open_tunnel(const gchar *server,
232 const gchar *tunnelcmd);
236 static SockInfo *imap_init_sock(SockInfo *sock, SSLType ssl_type);
238 static SockInfo *imap_init_sock(SockInfo *sock);
241 static gint imap_set_message_flags (IMAPSession *session,
242 MsgNumberList *numlist,
245 static gint imap_select (IMAPSession *session,
251 guint32 *uid_validity);
252 static gint imap_status (IMAPSession *session,
258 guint32 *uid_validity,
261 static void imap_parse_namespace (IMAPSession *session,
263 static void imap_get_namespace_by_list (IMAPSession *session,
265 static IMAPNameSpace *imap_find_namespace (IMAPFolder *folder,
267 static gchar imap_get_path_separator (IMAPFolder *folder,
269 static gchar *imap_get_real_path (IMAPFolder *folder,
272 static gchar *imap_parse_atom (SockInfo *sock,
277 static MsgFlags imap_parse_flags (const gchar *flag_str);
278 static MsgInfo *imap_parse_envelope (SockInfo *sock,
281 static gint imap_greeting (IMAPSession *session);
282 static gboolean imap_has_capability (IMAPSession *session,
284 void imap_free_capabilities (IMAPSession *session);
285 static const IMAPSet numberlist_to_imapset
286 (MsgNumberList *list);
288 /* low-level IMAP4rev1 commands */
289 static gint imap_cmd_login (IMAPSession *sock,
292 static gint imap_cmd_logout (IMAPSession *sock);
293 static gint imap_cmd_noop (IMAPSession *sock);
294 static gint imap_cmd_starttls (IMAPSession *sock);
295 static gint imap_cmd_namespace (IMAPSession *sock,
297 static gint imap_cmd_list (IMAPSession *session,
299 const gchar *mailbox,
301 static gint imap_cmd_do_select (IMAPSession *sock,
307 guint32 *uid_validity);
308 static gint imap_cmd_select (IMAPSession *sock,
313 guint32 *uid_validity);
314 static gint imap_cmd_examine (IMAPSession *sock,
319 guint32 *uid_validity);
320 static gint imap_cmd_create (IMAPSession *sock,
321 const gchar *folder);
322 static gint imap_cmd_rename (IMAPSession *sock,
323 const gchar *oldfolder,
324 const gchar *newfolder);
325 static gint imap_cmd_delete (IMAPSession *sock,
326 const gchar *folder);
327 static gint imap_cmd_envelope (IMAPSession *sock,
329 static gint imap_cmd_fetch (IMAPSession *sock,
331 const gchar *filename);
332 static gint imap_cmd_append (IMAPSession *session,
333 const gchar *destfolder,
336 static gint imap_cmd_copy (IMAPSession *session,
338 const gchar *destfolder,
340 static gint imap_cmd_store (IMAPSession *sock,
343 static gint imap_cmd_expunge (IMAPSession *sock);
345 static gint imap_cmd_ok (IMAPSession *session,
347 static void imap_gen_send (IMAPSession *sock,
348 const gchar *format, ...);
349 static gint imap_gen_recv (IMAPSession *sock,
352 /* misc utility functions */
353 static gchar *strchr_cpy (const gchar *src,
357 static gchar *get_quoted (const gchar *src,
361 static gchar *search_array_contain_str (GPtrArray *array,
363 static gchar *search_array_str (GPtrArray *array,
365 static void imap_path_separator_subst (gchar *str,
368 static gchar *imap_modified_utf7_to_locale (const gchar *mutf7_str);
369 static gchar *imap_locale_to_modified_utf7 (const gchar *from);
371 static gboolean imap_rename_folder_func (GNode *node,
373 static gint imap_get_num_list (Folder *folder,
376 static GSList *imap_get_msginfos (Folder *folder,
378 GSList *msgnum_list);
379 static MsgInfo *imap_get_msginfo (Folder *folder,
382 static gboolean imap_check_msgnum_validity (Folder *folder,
384 static void imap_change_flags (Folder *folder,
387 MsgPermFlags newflags);
389 FolderClass imap_class =
395 /* Folder functions */
401 /* FolderItem functions */
402 imap_folder_item_new,
403 imap_folder_item_destroy,
411 imap_check_msgnum_validity,
413 /* Message functions */
425 FolderClass *imap_get_class(void)
430 Folder *imap_folder_new(const gchar *name, const gchar *path)
434 folder = (Folder *)g_new0(IMAPFolder, 1);
435 folder->klass = &imap_class;
436 imap_folder_init(folder, name, path);
441 void imap_folder_destroy(Folder *folder)
445 dir = folder_get_path(folder);
446 if (is_dir_exist(dir))
447 remove_dir_recursive(dir);
450 folder_remote_folder_destroy(REMOTE_FOLDER(folder));
453 static void imap_folder_init(Folder *folder, const gchar *name,
456 folder_remote_folder_init((Folder *)folder, name, path);
459 static FolderItem *imap_folder_item_new(Folder *folder)
461 IMAPFolderItem *item;
463 item = g_new0(IMAPFolderItem, 1);
466 item->uid_list = NULL;
468 return (FolderItem *)item;
471 static void imap_folder_item_destroy(Folder *folder, FolderItem *_item)
473 IMAPFolderItem *item = (IMAPFolderItem *)_item;
475 g_return_if_fail(item != NULL);
476 g_slist_free(item->uid_list);
481 static gboolean imap_reset_uid_lists_func(GNode *node, gpointer data)
483 IMAPFolderItem *item = (IMAPFolderItem *)node->data;
487 g_slist_free(item->uid_list);
488 item->uid_list = NULL;
493 static void imap_reset_uid_lists(Folder *folder)
495 if(folder->node == NULL)
498 /* Destroy all uid lists and rest last uid */
499 g_node_traverse(folder->node, G_IN_ORDER, G_TRAVERSE_ALL, -1, imap_reset_uid_lists_func, NULL);
502 static IMAPSession *imap_session_get(Folder *folder)
504 RemoteFolder *rfolder = REMOTE_FOLDER(folder);
505 IMAPSession *session = NULL;
508 g_return_val_if_fail(folder != NULL, NULL);
509 g_return_val_if_fail(FOLDER_CLASS(folder) == &imap_class, NULL);
510 g_return_val_if_fail(folder->account != NULL, NULL);
513 port = folder->account->set_imapport ? folder->account->imapport
514 : folder->account->ssl_imap == SSL_TUNNEL
515 ? IMAPS_PORT : IMAP4_PORT;
517 port = folder->account->set_imapport ? folder->account->imapport
521 /* Make sure we have a session */
522 if (rfolder->session != NULL) {
523 session = IMAP_SESSION(rfolder->session);
525 imap_reset_uid_lists(folder);
526 session = imap_session_new(folder->account);
531 if (SESSION(session)->sock->state == CONN_DISCONNECTED) {
532 debug_print("IMAP server disconnected\n");
533 session_destroy(SESSION(session));
534 imap_reset_uid_lists(folder);
535 session = imap_session_new(folder->account);
538 /* Make sure session is authenticated */
539 if (!IMAP_SESSION(session)->authenticated)
540 imap_session_authenticate(IMAP_SESSION(session), folder->account);
541 if (!IMAP_SESSION(session)->authenticated) {
542 session_destroy(SESSION(session));
543 rfolder->session = NULL;
547 /* Make sure we have parsed the IMAP namespace */
548 imap_parse_namespace(IMAP_SESSION(session),
549 IMAP_FOLDER(folder));
551 /* I think the point of this code is to avoid sending a
552 * keepalive if we've used the session recently and therefore
553 * think it's still alive. Unfortunately, most of the code
554 * does not yet check for errors on the socket, and so if the
555 * connection drops we don't notice until the timeout expires.
556 * A better solution than sending a NOOP every time would be
557 * for every command to be prepared to retry until it is
558 * successfully sent. -- mbp */
559 if (time(NULL) - session->last_access_time > SESSION_TIMEOUT) {
560 /* verify that the session is still alive */
561 if (imap_cmd_noop(session) != IMAP_SUCCESS) {
562 /* Check if this is the first try to establish a
563 connection, if yes we don't try to reconnect */
564 if (rfolder->session == NULL) {
565 log_warning(_("Connecting %s:%d failed"),
566 folder->account->recv_server, port);
567 session_destroy(SESSION(session));
570 log_warning(_("IMAP4 connection to %s:%d has been"
571 " disconnected. Reconnecting...\n"),
572 folder->account->recv_server, port);
573 session_destroy(SESSION(session));
574 /* Clear folders session to make imap_session_get create
575 a new session, because of rfolder->session == NULL
576 it will not try to reconnect again and so avoid an
578 rfolder->session = NULL;
579 session = imap_session_get(folder);
584 rfolder->session = SESSION(session);
586 session->last_access_time = time(NULL);
588 return IMAP_SESSION(session);
591 IMAPSession *imap_session_new(const PrefsAccount *account)
593 IMAPSession *session;
598 /* FIXME: IMAP over SSL only... */
601 port = account->set_imapport ? account->imapport
602 : account->ssl_imap == SSL_TUNNEL ? IMAPS_PORT : IMAP4_PORT;
603 ssl_type = account->ssl_imap;
605 port = account->set_imapport ? account->imapport
609 if (account->set_tunnelcmd) {
610 log_message(_("creating tunneled IMAP4 connection\n"));
612 if ((imap_sock = imap_open_tunnel(account->recv_server,
616 if ((imap_sock = imap_open_tunnel(account->recv_server,
617 account->tunnelcmd)) == NULL)
621 g_return_val_if_fail(account->recv_server != NULL, NULL);
623 log_message(_("creating IMAP4 connection to %s:%d ...\n"),
624 account->recv_server, port);
627 if ((imap_sock = imap_open(account->recv_server, port,
630 if ((imap_sock = imap_open(account->recv_server, port)) == NULL)
635 session = g_new0(IMAPSession, 1);
636 session_init(SESSION(session));
637 SESSION(session)->type = SESSION_IMAP;
638 SESSION(session)->server = g_strdup(account->recv_server);
639 SESSION(session)->sock = imap_sock;
641 SESSION(session)->destroy = imap_session_destroy;
643 session->capability = NULL;
645 session->mbox = NULL;
646 session->authenticated = FALSE;
647 session->cmd_count = 0;
649 /* Only need to log in if the connection was not PREAUTH */
650 if (imap_greeting(session) != IMAP_SUCCESS) {
651 session_destroy(SESSION(session));
656 if (account->ssl_imap == SSL_STARTTLS && imap_has_capability(session, "STARTTLS")) {
659 ok = imap_cmd_starttls(session);
660 if (ok != IMAP_SUCCESS) {
661 log_warning(_("Can't start TLS session.\n"));
662 session_destroy(SESSION(session));
665 if (!ssl_init_socket_with_method(SESSION(session)->sock, SSL_METHOD_TLSv1)) {
666 session_destroy(SESSION(session));
670 imap_free_capabilities(session);
671 session->authenticated = FALSE;
672 session->cmd_count = 1;
674 if (imap_greeting(session) != IMAP_SUCCESS) {
675 session_destroy(SESSION(session));
680 log_message("IMAP connection is %s-authenticated\n",
681 (session->authenticated) ? "pre" : "un");
686 void imap_session_authenticate(IMAPSession *session, const PrefsAccount *account)
690 g_return_if_fail(account->userid != NULL);
692 pass = account->passwd;
695 tmp_pass = input_dialog_query_password(account->recv_server, account->userid);
698 Xstrdup_a(pass, tmp_pass, {g_free(tmp_pass); return;});
702 if (imap_cmd_login(session, account->userid, pass) != IMAP_SUCCESS) {
703 imap_cmd_logout(session);
707 session->authenticated = TRUE;
710 void imap_session_destroy(Session *session)
712 sock_close(session->sock);
713 session->sock = NULL;
715 g_free(IMAP_SESSION(session)->mbox);
716 imap_free_capabilities(IMAP_SESSION(session));
719 gchar *imap_fetch_msg(Folder *folder, FolderItem *item, gint uid)
721 gchar *path, *filename;
722 IMAPSession *session;
725 g_return_val_if_fail(folder != NULL, NULL);
726 g_return_val_if_fail(item != NULL, NULL);
728 path = folder_item_get_path(item);
729 if (!is_dir_exist(path))
731 filename = g_strconcat(path, G_DIR_SEPARATOR_S, itos(uid), NULL);
734 if (is_file_exist(filename)) {
735 debug_print("message %d has been already cached.\n", uid);
739 session = imap_session_get(folder);
745 ok = imap_select(session, IMAP_FOLDER(folder), item->path,
746 NULL, NULL, NULL, NULL);
747 if (ok != IMAP_SUCCESS) {
748 g_warning("can't select mailbox %s\n", item->path);
753 debug_print("getting message %d...\n", uid);
754 ok = imap_cmd_fetch(session, (guint32)uid, filename);
756 if (ok != IMAP_SUCCESS) {
757 g_warning("can't fetch message %d\n", uid);
765 gint imap_add_msg(Folder *folder, FolderItem *dest, const gchar *file,
766 gboolean remove_source)
769 IMAPSession *session;
772 g_return_val_if_fail(folder != NULL, -1);
773 g_return_val_if_fail(dest != NULL, -1);
774 g_return_val_if_fail(file != NULL, -1);
776 session = imap_session_get(folder);
777 if (!session) return -1;
779 destdir = imap_get_real_path(IMAP_FOLDER(folder), dest->path);
780 ok = imap_cmd_append(session, destdir, file, &newuid);
783 if (ok != IMAP_SUCCESS) {
784 g_warning("can't append message %s\n", file);
789 if (unlink(file) < 0)
790 FILE_OP_ERROR(file, "unlink");
796 static gint imap_do_copy(Folder *folder, FolderItem *dest, MsgInfo *msginfo,
797 gboolean remove_source)
800 IMAPSession *session;
804 g_return_val_if_fail(folder != NULL, -1);
805 g_return_val_if_fail(FOLDER_CLASS(folder) == &imap_class, -1);
806 g_return_val_if_fail(dest != NULL, -1);
807 g_return_val_if_fail(msginfo != NULL, -1);
809 session = imap_session_get(folder);
810 if (!session) return -1;
812 if (msginfo->folder == dest) {
813 g_warning("the src folder is identical to the dest.\n");
817 destdir = imap_get_real_path(IMAP_FOLDER(folder), dest->path);
819 /* ensure source folder selected */
820 ok = imap_select(session, IMAP_FOLDER(folder), msginfo->folder->path,
821 NULL, NULL, NULL, NULL);
822 if (ok != IMAP_SUCCESS)
826 debug_print("Moving message %s%c%d to %s ...\n",
827 msginfo->folder->path, G_DIR_SEPARATOR,
828 msginfo->msgnum, destdir);
830 debug_print("Copying message %s%c%d to %s ...\n",
831 msginfo->folder->path, G_DIR_SEPARATOR,
832 msginfo->msgnum, destdir);
834 ok = imap_cmd_copy(session, msginfo->msgnum, destdir, &newuid);
836 if (ok == IMAP_SUCCESS && remove_source) {
837 MsgNumberList numlist;
840 numlist.data = GINT_TO_POINTER(msginfo->msgnum);
842 imap_set_message_flags(session, &numlist,
843 IMAP_FLAG_DELETED, TRUE);
844 ok = imap_cmd_expunge(session);
849 if (ok == IMAP_SUCCESS)
855 gint imap_copy_msg(Folder *folder, FolderItem *dest, MsgInfo *msginfo)
860 g_return_val_if_fail(folder != NULL, -1);
861 g_return_val_if_fail(dest != NULL, -1);
862 g_return_val_if_fail(msginfo != NULL, -1);
863 g_return_val_if_fail(msginfo->folder != NULL, -1);
865 if (folder == msginfo->folder->folder)
866 return imap_do_copy(folder, dest, msginfo, FALSE);
868 srcfile = procmsg_get_message_file(msginfo);
869 if (!srcfile) return -1;
871 ret = imap_add_msg(folder, dest, srcfile, FALSE);
878 gint imap_remove_msg(Folder *folder, FolderItem *item, gint uid)
881 IMAPSession *session;
883 MsgNumberList numlist;
885 g_return_val_if_fail(folder != NULL, -1);
886 g_return_val_if_fail(FOLDER_CLASS(folder) == &imap_class, -1);
887 g_return_val_if_fail(item != NULL, -1);
889 session = imap_session_get(folder);
890 if (!session) return -1;
892 ok = imap_select(session, IMAP_FOLDER(folder), item->path,
893 NULL, NULL, NULL, NULL);
894 if (ok != IMAP_SUCCESS)
898 numlist.data = GINT_TO_POINTER(uid);
900 ok = imap_set_message_flags
901 (IMAP_SESSION(REMOTE_FOLDER(folder)->session),
902 &numlist, IMAP_FLAG_DELETED, TRUE);
903 if (ok != IMAP_SUCCESS) {
904 log_warning(_("can't set deleted flags: %d\n"), uid);
908 ok = imap_cmd_expunge(session);
909 if (ok != IMAP_SUCCESS) {
910 log_warning(_("can't expunge\n"));
914 dir = folder_item_get_path(item);
915 if (is_dir_exist(dir))
916 remove_numbered_files(dir, uid, uid);
922 gint imap_remove_all_msg(Folder *folder, FolderItem *item)
924 gint exists, recent, unseen;
925 guint32 uid_validity;
927 IMAPSession *session;
930 g_return_val_if_fail(folder != NULL, -1);
931 g_return_val_if_fail(item != NULL, -1);
933 session = imap_session_get(folder);
934 if (!session) return -1;
936 ok = imap_select(session, IMAP_FOLDER(folder), item->path,
937 &exists, &recent, &unseen, &uid_validity);
938 if (ok != IMAP_SUCCESS)
943 imap_gen_send(session,
944 "STORE 1:%d +FLAGS (\\Deleted)", exists);
945 ok = imap_cmd_ok(session, NULL);
946 if (ok != IMAP_SUCCESS) {
947 log_warning(_("can't set deleted flags: 1:%d\n"), exists);
951 ok = imap_cmd_expunge(session);
952 if (ok != IMAP_SUCCESS) {
953 log_warning(_("can't expunge\n"));
957 dir = folder_item_get_path(item);
958 if (is_dir_exist(dir))
959 remove_all_numbered_files(dir);
965 gboolean imap_is_msg_changed(Folder *folder, FolderItem *item, MsgInfo *msginfo)
967 /* TODO: properly implement this method */
971 void imap_scan_tree(Folder *folder)
974 IMAPSession *session;
975 gchar *root_folder = NULL;
977 g_return_if_fail(folder != NULL);
978 g_return_if_fail(folder->account != NULL);
980 session = imap_session_get(folder);
983 folder_tree_destroy(folder);
984 item = folder_item_new(folder, folder->name, NULL);
985 item->folder = folder;
986 folder->node = g_node_new(item);
991 if (folder->account->imap_dir && *folder->account->imap_dir) {
992 Xstrdup_a(root_folder, folder->account->imap_dir, return);
993 strtailchomp(root_folder, '/');
994 debug_print("IMAP root directory: %s\n", root_folder);
997 item = folder_item_new(folder, folder->name, root_folder);
998 item->folder = folder;
999 item->no_select = TRUE;
1000 folder->node = g_node_new(item);
1002 imap_scan_tree_recursive(session, item);
1004 imap_create_missing_folders(folder);
1007 static gint imap_scan_tree_recursive(IMAPSession *session, FolderItem *item)
1010 IMAPFolder *imapfolder;
1011 FolderItem *new_item;
1012 GSList *item_list, *cur;
1014 gchar *wildcard_path;
1018 g_return_val_if_fail(item != NULL, -1);
1019 g_return_val_if_fail(item->folder != NULL, -1);
1020 g_return_val_if_fail(item->no_sub == FALSE, -1);
1022 folder = FOLDER(item->folder);
1023 imapfolder = IMAP_FOLDER(folder);
1025 separator = imap_get_path_separator(imapfolder, item->path);
1027 if (item->folder->ui_func)
1028 item->folder->ui_func(folder, item, folder->ui_func_data);
1031 wildcard[0] = separator;
1034 real_path = imap_get_real_path(imapfolder, item->path);
1038 real_path = g_strdup("");
1041 Xstrcat_a(wildcard_path, real_path, wildcard,
1042 {g_free(real_path); return IMAP_ERROR;});
1043 QUOTE_IF_REQUIRED(wildcard_path, wildcard_path);
1045 imap_gen_send(session, "LIST \"\" %s",
1048 strtailchomp(real_path, separator);
1049 item_list = imap_parse_list(imapfolder, session, real_path, NULL);
1052 for (cur = item_list; cur != NULL; cur = cur->next) {
1053 new_item = cur->data;
1054 if (!strcmp(new_item->path, "INBOX")) {
1055 if (!folder->inbox) {
1056 new_item->stype = F_INBOX;
1057 item->folder->inbox = new_item;
1059 folder_item_destroy(new_item);
1062 } else if (!item->parent || item->stype == F_INBOX) {
1065 base = g_basename(new_item->path);
1067 if (!folder->outbox && !strcasecmp(base, "Sent")) {
1068 new_item->stype = F_OUTBOX;
1069 folder->outbox = new_item;
1070 } else if (!folder->draft && !strcasecmp(base, "Drafts")) {
1071 new_item->stype = F_DRAFT;
1072 folder->draft = new_item;
1073 } else if (!folder->queue && !strcasecmp(base, "Queue")) {
1074 new_item->stype = F_QUEUE;
1075 folder->queue = new_item;
1076 } else if (!folder->trash && !strcasecmp(base, "Trash")) {
1077 new_item->stype = F_TRASH;
1078 folder->trash = new_item;
1081 folder_item_append(item, new_item);
1082 if (new_item->no_sub == FALSE)
1083 imap_scan_tree_recursive(session, new_item);
1086 return IMAP_SUCCESS;
1089 static GSList *imap_parse_list(IMAPFolder *folder, IMAPSession *session,
1090 const gchar *real_path, gchar *separator)
1092 gchar buf[IMAPBUFSIZE];
1094 gchar separator_str[16];
1097 gchar *loc_name, *loc_path;
1098 GSList *item_list = NULL;
1100 FolderItem *new_item;
1102 debug_print("getting list of %s ...\n",
1103 *real_path ? real_path : "\"\"");
1105 str = g_string_new(NULL);
1108 if (sock_gets(SESSION(session)->sock, buf, sizeof(buf)) <= 0) {
1109 log_warning(_("error occurred while getting LIST.\n"));
1113 if (buf[0] != '*' || buf[1] != ' ') {
1114 log_print("IMAP4< %s\n", buf);
1117 debug_print("IMAP4< %s\n", buf);
1119 g_string_assign(str, buf);
1121 if (strncmp(p, "LIST ", 5) != 0) continue;
1124 if (*p != '(') continue;
1126 p = strchr_cpy(p, ')', flags, sizeof(flags));
1128 while (*p == ' ') p++;
1130 p = strchr_cpy(p, ' ', separator_str, sizeof(separator_str));
1132 extract_quote(separator_str, '"');
1133 if (!strcmp(separator_str, "NIL"))
1134 separator_str[0] = '\0';
1136 *separator = separator_str[0];
1139 while (*p == ' ') p++;
1140 if (*p == '{' || *p == '"')
1141 p = imap_parse_atom(SESSION(session)->sock, p,
1142 buf, sizeof(buf), str);
1144 strncpy2(buf, p, sizeof(buf));
1145 strtailchomp(buf, separator_str[0]);
1146 if (buf[0] == '\0') continue;
1147 if (!strcmp(buf, real_path)) continue;
1149 if (separator_str[0] != '\0')
1150 subst_char(buf, separator_str[0], '/');
1151 name = g_basename(buf);
1152 if (name[0] == '.') continue;
1154 loc_name = imap_modified_utf7_to_locale(name);
1155 loc_path = imap_modified_utf7_to_locale(buf);
1156 new_item = folder_item_new(FOLDER(folder), loc_name, loc_path);
1157 if (strcasestr(flags, "\\Noinferiors") != NULL)
1158 new_item->no_sub = TRUE;
1159 if (strcmp(buf, "INBOX") != 0 &&
1160 strcasestr(flags, "\\Noselect") != NULL)
1161 new_item->no_select = TRUE;
1163 item_list = g_slist_append(item_list, new_item);
1165 debug_print("folder %s has been added.\n", loc_path);
1170 g_string_free(str, TRUE);
1175 gint imap_create_tree(Folder *folder)
1177 g_return_val_if_fail(folder != NULL, -1);
1178 g_return_val_if_fail(folder->node != NULL, -1);
1179 g_return_val_if_fail(folder->node->data != NULL, -1);
1180 g_return_val_if_fail(folder->account != NULL, -1);
1182 imap_scan_tree(folder);
1183 imap_create_missing_folders(folder);
1188 static void imap_create_missing_folders(Folder *folder)
1190 g_return_if_fail(folder != NULL);
1193 folder->inbox = imap_create_special_folder
1194 (folder, F_INBOX, "INBOX");
1196 if (!folder->outbox)
1197 folder->outbox = imap_create_special_folder
1198 (folder, F_OUTBOX, "Sent");
1200 folder->draft = imap_create_special_folder
1201 (folder, F_DRAFT, "Drafts");
1203 folder->queue = imap_create_special_folder
1204 (folder, F_QUEUE, "Queue");
1207 folder->trash = imap_create_special_folder
1208 (folder, F_TRASH, "Trash");
1211 static FolderItem *imap_create_special_folder(Folder *folder,
1212 SpecialFolderItemType stype,
1216 FolderItem *new_item;
1218 g_return_val_if_fail(folder != NULL, NULL);
1219 g_return_val_if_fail(folder->node != NULL, NULL);
1220 g_return_val_if_fail(folder->node->data != NULL, NULL);
1221 g_return_val_if_fail(folder->account != NULL, NULL);
1222 g_return_val_if_fail(name != NULL, NULL);
1224 item = FOLDER_ITEM(folder->node->data);
1225 new_item = imap_create_folder(folder, item, name);
1228 g_warning("Can't create '%s'\n", name);
1229 if (!folder->inbox) return NULL;
1231 new_item = imap_create_folder(folder, folder->inbox, name);
1233 g_warning("Can't create '%s' under INBOX\n", name);
1235 new_item->stype = stype;
1237 new_item->stype = stype;
1242 FolderItem *imap_create_folder(Folder *folder, FolderItem *parent,
1245 gchar *dirpath, *imap_path;
1246 IMAPSession *session;
1247 FolderItem *new_item;
1253 g_return_val_if_fail(folder != NULL, NULL);
1254 g_return_val_if_fail(folder->account != NULL, NULL);
1255 g_return_val_if_fail(parent != NULL, NULL);
1256 g_return_val_if_fail(name != NULL, NULL);
1258 session = imap_session_get(folder);
1259 if (!session) return NULL;
1261 if (!parent->parent && strcmp(name, "INBOX") == 0)
1262 dirpath = g_strdup(name);
1263 else if (parent->path)
1264 dirpath = g_strconcat(parent->path, "/", name, NULL);
1265 else if ((p = strchr(name, '/')) != NULL && *(p + 1) != '\0')
1266 dirpath = g_strdup(name);
1267 else if (folder->account->imap_dir && *folder->account->imap_dir) {
1270 Xstrdup_a(imap_dir, folder->account->imap_dir, return NULL);
1271 strtailchomp(imap_dir, '/');
1272 dirpath = g_strconcat(imap_dir, "/", name, NULL);
1274 dirpath = g_strdup(name);
1276 /* keep trailing directory separator to create a folder that contains
1278 imap_path = imap_locale_to_modified_utf7(dirpath);
1279 strtailchomp(dirpath, '/');
1280 Xstrdup_a(new_name, name, {g_free(dirpath); return NULL;});
1281 strtailchomp(new_name, '/');
1282 separator = imap_get_path_separator(IMAP_FOLDER(folder), imap_path);
1283 imap_path_separator_subst(imap_path, separator);
1284 subst_char(new_name, '/', separator);
1286 if (strcmp(name, "INBOX") != 0) {
1289 gboolean exist = FALSE;
1291 argbuf = g_ptr_array_new();
1292 ok = imap_cmd_list(session, NULL, imap_path,
1294 if (ok != IMAP_SUCCESS) {
1295 log_warning(_("can't create mailbox: LIST failed\n"));
1298 ptr_array_free_strings(argbuf);
1299 g_ptr_array_free(argbuf, TRUE);
1303 for (i = 0; i < argbuf->len; i++) {
1305 str = g_ptr_array_index(argbuf, i);
1306 if (!strncmp(str, "LIST ", 5)) {
1311 ptr_array_free_strings(argbuf);
1312 g_ptr_array_free(argbuf, TRUE);
1315 ok = imap_cmd_create(session, imap_path);
1316 if (ok != IMAP_SUCCESS) {
1317 log_warning(_("can't create mailbox\n"));
1325 new_item = folder_item_new(folder, new_name, dirpath);
1326 folder_item_append(parent, new_item);
1330 dirpath = folder_item_get_path(new_item);
1331 if (!is_dir_exist(dirpath))
1332 make_dir_hier(dirpath);
1338 gint imap_rename_folder(Folder *folder, FolderItem *item, const gchar *name)
1342 gchar *real_oldpath;
1343 gchar *real_newpath;
1346 gchar *old_cache_dir;
1347 gchar *new_cache_dir;
1348 IMAPSession *session;
1351 gint exists, recent, unseen;
1352 guint32 uid_validity;
1354 g_return_val_if_fail(folder != NULL, -1);
1355 g_return_val_if_fail(item != NULL, -1);
1356 g_return_val_if_fail(item->path != NULL, -1);
1357 g_return_val_if_fail(name != NULL, -1);
1359 session = imap_session_get(folder);
1360 if (!session) return -1;
1362 real_oldpath = imap_get_real_path(IMAP_FOLDER(folder), item->path);
1364 g_free(session->mbox);
1365 session->mbox = NULL;
1366 ok = imap_cmd_examine(session, "INBOX",
1367 &exists, &recent, &unseen, &uid_validity);
1368 if (ok != IMAP_SUCCESS) {
1369 g_free(real_oldpath);
1373 separator = imap_get_path_separator(IMAP_FOLDER(folder), item->path);
1374 if (strchr(item->path, G_DIR_SEPARATOR)) {
1375 dirpath = g_dirname(item->path);
1376 newpath = g_strconcat(dirpath, G_DIR_SEPARATOR_S, name, NULL);
1379 newpath = g_strdup(name);
1381 real_newpath = imap_locale_to_modified_utf7(newpath);
1382 imap_path_separator_subst(real_newpath, separator);
1384 ok = imap_cmd_rename(session, real_oldpath, real_newpath);
1385 if (ok != IMAP_SUCCESS) {
1386 log_warning(_("can't rename mailbox: %s to %s\n"),
1387 real_oldpath, real_newpath);
1388 g_free(real_oldpath);
1390 g_free(real_newpath);
1395 item->name = g_strdup(name);
1397 old_cache_dir = folder_item_get_path(item);
1399 node = g_node_find(item->folder->node, G_PRE_ORDER, G_TRAVERSE_ALL,
1401 paths[0] = g_strdup(item->path);
1403 g_node_traverse(node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
1404 imap_rename_folder_func, paths);
1406 if (is_dir_exist(old_cache_dir)) {
1407 new_cache_dir = folder_item_get_path(item);
1408 if (rename(old_cache_dir, new_cache_dir) < 0) {
1409 FILE_OP_ERROR(old_cache_dir, "rename");
1411 g_free(new_cache_dir);
1414 g_free(old_cache_dir);
1417 g_free(real_oldpath);
1418 g_free(real_newpath);
1423 gint imap_remove_folder(Folder *folder, FolderItem *item)
1426 IMAPSession *session;
1429 gint exists, recent, unseen;
1430 guint32 uid_validity;
1432 g_return_val_if_fail(folder != NULL, -1);
1433 g_return_val_if_fail(item != NULL, -1);
1434 g_return_val_if_fail(item->path != NULL, -1);
1436 session = imap_session_get(folder);
1437 if (!session) return -1;
1439 path = imap_get_real_path(IMAP_FOLDER(folder), item->path);
1441 ok = imap_cmd_examine(session, "INBOX",
1442 &exists, &recent, &unseen, &uid_validity);
1443 if (ok != IMAP_SUCCESS) {
1448 ok = imap_cmd_delete(session, path);
1449 if (ok != IMAP_SUCCESS) {
1450 log_warning(_("can't delete mailbox\n"));
1456 cache_dir = folder_item_get_path(item);
1457 if (is_dir_exist(cache_dir) && remove_dir_recursive(cache_dir) < 0)
1458 g_warning("can't remove directory '%s'\n", cache_dir);
1460 folder_item_remove(item);
1465 static GSList *imap_get_uncached_messages(IMAPSession *session,
1467 MsgNumberList *numlist)
1470 GSList *newlist = NULL;
1471 GSList *llast = NULL;
1476 g_return_val_if_fail(session != NULL, NULL);
1477 g_return_val_if_fail(item != NULL, NULL);
1478 g_return_val_if_fail(item->folder != NULL, NULL);
1479 g_return_val_if_fail(FOLDER_CLASS(item->folder) == &imap_class, NULL);
1481 imapset = numberlist_to_imapset(numlist);
1482 while (imapset != NULL) {
1483 if (imap_cmd_envelope(session, imapset)
1485 log_warning(_("can't get envelope\n"));
1489 str = g_string_new(NULL);
1492 if ((tmp = sock_getline(SESSION(session)->sock)) == NULL) {
1493 log_warning(_("error occurred while getting envelope.\n"));
1494 g_string_free(str, TRUE);
1498 if (tmp[0] != '*' || tmp[1] != ' ') {
1499 log_print("IMAP4< %s\n", tmp);
1503 if (strstr(tmp, "FETCH") == NULL) {
1504 log_print("IMAP4< %s\n", tmp);
1508 log_print("IMAP4< %s\n", tmp);
1509 g_string_assign(str, tmp);
1512 msginfo = imap_parse_envelope
1513 (SESSION(session)->sock, item, str);
1515 log_warning(_("can't parse envelope: %s\n"), str->str);
1518 if (item->stype == F_QUEUE) {
1519 MSG_SET_TMP_FLAGS(msginfo->flags, MSG_QUEUED);
1520 } else if (item->stype == F_DRAFT) {
1521 MSG_SET_TMP_FLAGS(msginfo->flags, MSG_DRAFT);
1524 msginfo->folder = item;
1527 llast = newlist = g_slist_append(newlist, msginfo);
1529 llast = g_slist_append(llast, msginfo);
1530 llast = llast->next;
1534 g_string_free(str, TRUE);
1535 imapset = numberlist_to_imapset(NULL);
1541 static void imap_delete_all_cached_messages(FolderItem *item)
1545 g_return_if_fail(item != NULL);
1546 g_return_if_fail(item->folder != NULL);
1547 g_return_if_fail(FOLDER_CLASS(item->folder) == &imap_class);
1549 debug_print("Deleting all cached messages...\n");
1551 dir = folder_item_get_path(item);
1552 if (is_dir_exist(dir))
1553 remove_all_numbered_files(dir);
1556 debug_print("done.\n");
1560 static SockInfo *imap_open_tunnel(const gchar *server,
1561 const gchar *tunnelcmd,
1564 static SockInfo *imap_open_tunnel(const gchar *server,
1565 const gchar *tunnelcmd)
1570 if ((sock = sock_connect_cmd(server, tunnelcmd)) == NULL) {
1571 log_warning(_("Can't establish IMAP4 session with: %s\n"),
1576 return imap_init_sock(sock, ssl_type);
1578 return imap_init_sock(sock);
1584 static SockInfo *imap_open(const gchar *server, gushort port,
1587 static SockInfo *imap_open(const gchar *server, gushort port)
1592 if ((sock = sock_connect(server, port)) == NULL) {
1593 log_warning(_("Can't connect to IMAP4 server: %s:%d\n"),
1599 if (ssl_type == SSL_TUNNEL && !ssl_init_socket(sock)) {
1600 log_warning(_("Can't establish IMAP4 session with: %s:%d\n"),
1610 static SockInfo *imap_init_sock(SockInfo *sock, SSLType ssl_type)
1612 static SockInfo *imap_init_sock(SockInfo *sock)
1619 static GList *imap_parse_namespace_str(gchar *str)
1624 IMAPNameSpace *namespace;
1625 GList *ns_list = NULL;
1627 while (*p != '\0') {
1628 /* parse ("#foo" "/") */
1630 while (*p && *p != '(') p++;
1631 if (*p == '\0') break;
1634 while (*p && *p != '"') p++;
1635 if (*p == '\0') break;
1639 while (*p && *p != '"') p++;
1640 if (*p == '\0') break;
1644 while (*p && isspace(*p)) p++;
1645 if (*p == '\0') break;
1646 if (strncmp(p, "NIL", 3) == 0)
1648 else if (*p == '"') {
1651 while (*p && *p != '"') p++;
1652 if (*p == '\0') break;
1657 while (*p && *p != ')') p++;
1658 if (*p == '\0') break;
1661 namespace = g_new(IMAPNameSpace, 1);
1662 namespace->name = g_strdup(name);
1663 namespace->separator = separator ? separator[0] : '\0';
1664 ns_list = g_list_append(ns_list, namespace);
1670 static void imap_parse_namespace(IMAPSession *session, IMAPFolder *folder)
1675 g_return_if_fail(session != NULL);
1676 g_return_if_fail(folder != NULL);
1678 if (folder->ns_personal != NULL ||
1679 folder->ns_others != NULL ||
1680 folder->ns_shared != NULL)
1683 if (!imap_has_capability(session, "NAMESPACE")) {
1684 imap_get_namespace_by_list(session, folder);
1688 if (imap_cmd_namespace(session, &ns_str)
1690 log_warning(_("can't get namespace\n"));
1694 str_array = strsplit_parenthesis(ns_str, '(', ')', 3);
1695 if (str_array == NULL) {
1697 imap_get_namespace_by_list(session, folder);
1701 folder->ns_personal = imap_parse_namespace_str(str_array[0]);
1702 if (str_array[0] && str_array[1])
1703 folder->ns_others = imap_parse_namespace_str(str_array[1]);
1704 if (str_array[0] && str_array[1] && str_array[2])
1705 folder->ns_shared = imap_parse_namespace_str(str_array[2]);
1706 g_strfreev(str_array);
1710 static void imap_get_namespace_by_list(IMAPSession *session, IMAPFolder *folder)
1712 GSList *item_list, *cur;
1713 gchar separator = '\0';
1714 IMAPNameSpace *namespace;
1716 g_return_if_fail(session != NULL);
1717 g_return_if_fail(folder != NULL);
1719 if (folder->ns_personal != NULL ||
1720 folder->ns_others != NULL ||
1721 folder->ns_shared != NULL)
1724 imap_gen_send(session, "LIST \"\" \"\"");
1725 item_list = imap_parse_list(folder, session, "", &separator);
1726 for (cur = item_list; cur != NULL; cur = cur->next)
1727 folder_item_destroy(FOLDER_ITEM(cur->data));
1728 g_slist_free(item_list);
1730 namespace = g_new(IMAPNameSpace, 1);
1731 namespace->name = g_strdup("");
1732 namespace->separator = separator;
1733 folder->ns_personal = g_list_append(NULL, namespace);
1736 static IMAPNameSpace *imap_find_namespace_from_list(GList *ns_list,
1739 IMAPNameSpace *namespace = NULL;
1740 gchar *tmp_path, *name;
1742 if (!path) path = "";
1744 Xstrcat_a(tmp_path, path, "/", return NULL);
1746 for (; ns_list != NULL; ns_list = ns_list->next) {
1747 IMAPNameSpace *tmp_ns = ns_list->data;
1749 Xstrdup_a(name, tmp_ns->name, return namespace);
1750 if (tmp_ns->separator && tmp_ns->separator != '/')
1751 subst_char(name, tmp_ns->separator, '/');
1752 if (strncmp(tmp_path, name, strlen(name)) == 0)
1759 static IMAPNameSpace *imap_find_namespace(IMAPFolder *folder,
1762 IMAPNameSpace *namespace;
1764 g_return_val_if_fail(folder != NULL, NULL);
1766 namespace = imap_find_namespace_from_list(folder->ns_personal, path);
1767 if (namespace) return namespace;
1768 namespace = imap_find_namespace_from_list(folder->ns_others, path);
1769 if (namespace) return namespace;
1770 namespace = imap_find_namespace_from_list(folder->ns_shared, path);
1771 if (namespace) return namespace;
1776 static gchar imap_get_path_separator(IMAPFolder *folder, const gchar *path)
1778 IMAPNameSpace *namespace;
1779 gchar separator = '/';
1781 namespace = imap_find_namespace(folder, path);
1782 if (namespace && namespace->separator)
1783 separator = namespace->separator;
1788 static gchar *imap_get_real_path(IMAPFolder *folder, const gchar *path)
1793 g_return_val_if_fail(folder != NULL, NULL);
1794 g_return_val_if_fail(path != NULL, NULL);
1796 real_path = imap_locale_to_modified_utf7(path);
1797 separator = imap_get_path_separator(folder, path);
1798 imap_path_separator_subst(real_path, separator);
1803 static gchar *imap_parse_atom(SockInfo *sock, gchar *src,
1804 gchar *dest, gint dest_len, GString *str)
1806 gchar *cur_pos = src;
1809 g_return_val_if_fail(str != NULL, cur_pos);
1811 /* read the next line if the current response buffer is empty */
1812 while (isspace(*cur_pos)) cur_pos++;
1813 while (*cur_pos == '\0') {
1814 if ((nextline = sock_getline(sock)) == NULL)
1816 g_string_assign(str, nextline);
1818 strretchomp(nextline);
1819 /* log_print("IMAP4< %s\n", nextline); */
1820 debug_print("IMAP4< %s\n", nextline);
1823 while (isspace(*cur_pos)) cur_pos++;
1826 if (!strncmp(cur_pos, "NIL", 3)) {
1829 } else if (*cur_pos == '\"') {
1832 p = get_quoted(cur_pos, '\"', dest, dest_len);
1833 cur_pos = p ? p : cur_pos + 2;
1834 } else if (*cur_pos == '{') {
1839 cur_pos = strchr_cpy(cur_pos + 1, '}', buf, sizeof(buf));
1841 g_return_val_if_fail(len > 0, cur_pos);
1843 g_string_truncate(str, 0);
1847 if ((nextline = sock_getline(sock)) == NULL)
1849 line_len += strlen(nextline);
1850 g_string_append(str, nextline);
1852 strretchomp(nextline);
1853 /* log_print("IMAP4< %s\n", nextline); */
1854 debug_print("IMAP4< %s\n", nextline);
1856 } while (line_len < len);
1858 memcpy(dest, cur_pos, MIN(len, dest_len - 1));
1859 dest[MIN(len, dest_len - 1)] = '\0';
1866 static gchar *imap_get_header(SockInfo *sock, gchar *cur_pos, gchar **headers,
1876 g_return_val_if_fail(str != NULL, cur_pos);
1878 while (isspace(*cur_pos)) cur_pos++;
1880 g_return_val_if_fail(*cur_pos == '{', cur_pos);
1882 cur_pos = strchr_cpy(cur_pos + 1, '}', buf, sizeof(buf));
1884 g_return_val_if_fail(len > 0, cur_pos);
1886 g_string_truncate(str, 0);
1890 if ((nextline = sock_getline(sock)) == NULL)
1892 block_len += strlen(nextline);
1893 g_string_append(str, nextline);
1895 strretchomp(nextline);
1896 /* debug_print("IMAP4< %s\n", nextline); */
1898 } while (block_len < len);
1900 debug_print("IMAP4< [contents of BODY.PEEK[HEADER.FIELDS (...)]]\n");
1902 *headers = g_strndup(cur_pos, len);
1905 while (isspace(*cur_pos)) cur_pos++;
1906 while (*cur_pos == '\0') {
1907 if ((nextline = sock_getline(sock)) == NULL)
1909 g_string_assign(str, nextline);
1911 strretchomp(nextline);
1912 debug_print("IMAP4< %s\n", nextline);
1915 while (isspace(*cur_pos)) cur_pos++;
1921 static MsgFlags imap_parse_flags(const gchar *flag_str)
1923 const gchar *p = flag_str;
1924 MsgFlags flags = {0, 0};
1926 flags.perm_flags = MSG_UNREAD;
1928 while ((p = strchr(p, '\\')) != NULL) {
1931 if (g_strncasecmp(p, "Recent", 6) == 0 && MSG_IS_UNREAD(flags)) {
1932 MSG_SET_PERM_FLAGS(flags, MSG_NEW);
1933 } else if (g_strncasecmp(p, "Seen", 4) == 0) {
1934 MSG_UNSET_PERM_FLAGS(flags, MSG_NEW|MSG_UNREAD);
1935 } else if (g_strncasecmp(p, "Deleted", 7) == 0) {
1936 MSG_SET_PERM_FLAGS(flags, MSG_DELETED);
1937 } else if (g_strncasecmp(p, "Flagged", 7) == 0) {
1938 MSG_SET_PERM_FLAGS(flags, MSG_MARKED);
1939 } else if (g_strncasecmp(p, "Answered", 8) == 0) {
1940 MSG_SET_PERM_FLAGS(flags, MSG_REPLIED);
1947 static MsgInfo *imap_parse_envelope(SockInfo *sock, FolderItem *item,
1950 gchar buf[IMAPBUFSIZE];
1951 MsgInfo *msginfo = NULL;
1956 MsgFlags flags = {0, 0}, imap_flags = {0, 0};
1958 g_return_val_if_fail(line_str != NULL, NULL);
1959 g_return_val_if_fail(line_str->str[0] == '*' &&
1960 line_str->str[1] == ' ', NULL);
1962 MSG_SET_TMP_FLAGS(flags, MSG_IMAP);
1963 if (item->stype == F_QUEUE) {
1964 MSG_SET_TMP_FLAGS(flags, MSG_QUEUED);
1965 } else if (item->stype == F_DRAFT) {
1966 MSG_SET_TMP_FLAGS(flags, MSG_DRAFT);
1969 cur_pos = line_str->str + 2;
1971 #define PARSE_ONE_ELEMENT(ch) \
1973 cur_pos = strchr_cpy(cur_pos, ch, buf, sizeof(buf)); \
1974 if (cur_pos == NULL) { \
1975 g_warning("cur_pos == NULL\n"); \
1976 procmsg_msginfo_free(msginfo); \
1981 PARSE_ONE_ELEMENT(' ');
1984 PARSE_ONE_ELEMENT(' ');
1985 g_return_val_if_fail(!strcmp(buf, "FETCH"), NULL);
1987 g_return_val_if_fail(*cur_pos == '(', NULL);
1990 while (*cur_pos != '\0' && *cur_pos != ')') {
1991 while (*cur_pos == ' ') cur_pos++;
1993 if (!strncmp(cur_pos, "UID ", 4)) {
1995 uid = strtoul(cur_pos, &cur_pos, 10);
1996 } else if (!strncmp(cur_pos, "FLAGS ", 6)) {
1998 if (*cur_pos != '(') {
1999 g_warning("*cur_pos != '('\n");
2000 procmsg_msginfo_free(msginfo);
2004 PARSE_ONE_ELEMENT(')');
2005 imap_flags = imap_parse_flags(buf);
2006 } else if (!strncmp(cur_pos, "RFC822.SIZE ", 12)) {
2008 size = strtol(cur_pos, &cur_pos, 10);
2009 } else if (!strncmp(cur_pos, "BODY[HEADER.FIELDS ", 19)) {
2013 if (*cur_pos != '(') {
2014 g_warning("*cur_pos != '('\n");
2015 procmsg_msginfo_free(msginfo);
2019 PARSE_ONE_ELEMENT(')');
2020 if (*cur_pos != ']') {
2021 g_warning("*cur_pos != ']'\n");
2022 procmsg_msginfo_free(msginfo);
2027 cur_pos = imap_get_header(sock, cur_pos, &headers,
2029 msginfo = procheader_parse_str(headers, flags, FALSE, FALSE);
2032 g_warning("invalid FETCH response: %s\n", cur_pos);
2038 msginfo->msgnum = uid;
2039 msginfo->size = size;
2040 msginfo->flags.tmp_flags |= imap_flags.tmp_flags;
2041 msginfo->flags.perm_flags = imap_flags.perm_flags;
2047 static gint imap_set_message_flags(IMAPSession *session,
2048 MsgNumberList *numlist,
2056 buf = g_string_new(is_set ? "+FLAGS (" : "-FLAGS (");
2058 if (IMAP_IS_SEEN(flags)) g_string_append(buf, "\\Seen ");
2059 if (IMAP_IS_ANSWERED(flags)) g_string_append(buf, "\\Answered ");
2060 if (IMAP_IS_FLAGGED(flags)) g_string_append(buf, "\\Flagged ");
2061 if (IMAP_IS_DELETED(flags)) g_string_append(buf, "\\Deleted ");
2062 if (IMAP_IS_DRAFT(flags)) g_string_append(buf, "\\Draft");
2064 if (buf->str[buf->len - 1] == ' ')
2065 g_string_truncate(buf, buf->len - 1);
2067 g_string_append_c(buf, ')');
2069 imapset = numberlist_to_imapset(numlist);
2070 while (imapset != NULL) {
2071 ok = imap_cmd_store(session, imapset,
2073 imapset = numberlist_to_imapset(NULL);
2075 g_string_free(buf, TRUE);
2080 static gint imap_select(IMAPSession *session, IMAPFolder *folder,
2082 gint *exists, gint *recent, gint *unseen,
2083 guint32 *uid_validity)
2087 gint exists_, recent_, unseen_, uid_validity_;
2089 if (!exists || !recent || !unseen || !uid_validity) {
2090 if (session->mbox && strcmp(session->mbox, path) == 0)
2091 return IMAP_SUCCESS;
2095 uid_validity = &uid_validity_;
2098 g_free(session->mbox);
2099 session->mbox = NULL;
2101 real_path = imap_get_real_path(folder, path);
2102 ok = imap_cmd_select(session, real_path,
2103 exists, recent, unseen, uid_validity);
2104 if (ok != IMAP_SUCCESS)
2105 log_warning(_("can't select folder: %s\n"), real_path);
2107 session->mbox = g_strdup(path);
2108 session->folder_content_changed = FALSE;
2115 #define THROW(err) { ok = err; goto catch; }
2117 static gint imap_status(IMAPSession *session, IMAPFolder *folder,
2119 gint *messages, gint *recent,
2120 guint32 *uid_next, guint32 *uid_validity,
2129 *messages = *recent = *uid_next = *uid_validity = *unseen = 0;
2131 argbuf = g_ptr_array_new();
2133 real_path = imap_get_real_path(folder, path);
2134 QUOTE_IF_REQUIRED(real_path_, real_path);
2135 imap_gen_send(session, "STATUS %s "
2136 "(MESSAGES RECENT UIDNEXT UIDVALIDITY UNSEEN)",
2139 ok = imap_cmd_ok(session, argbuf);
2140 if (ok != IMAP_SUCCESS) THROW(ok);
2142 str = search_array_str(argbuf, "STATUS");
2143 if (!str) THROW(IMAP_ERROR);
2145 str = strchr(str, '(');
2146 if (!str) THROW(IMAP_ERROR);
2148 while (*str != '\0' && *str != ')') {
2149 while (*str == ' ') str++;
2151 if (!strncmp(str, "MESSAGES ", 9)) {
2153 *messages = strtol(str, &str, 10);
2154 } else if (!strncmp(str, "RECENT ", 7)) {
2156 *recent = strtol(str, &str, 10);
2157 } else if (!strncmp(str, "UIDNEXT ", 8)) {
2159 *uid_next = strtoul(str, &str, 10);
2160 } else if (!strncmp(str, "UIDVALIDITY ", 12)) {
2162 *uid_validity = strtoul(str, &str, 10);
2163 } else if (!strncmp(str, "UNSEEN ", 7)) {
2165 *unseen = strtol(str, &str, 10);
2167 g_warning("invalid STATUS response: %s\n", str);
2174 ptr_array_free_strings(argbuf);
2175 g_ptr_array_free(argbuf, TRUE);
2183 /* low-level IMAP4rev1 commands */
2185 static gint imap_cmd_login(IMAPSession *session,
2186 const gchar *user, const gchar *pass)
2188 gchar *user_, *pass_;
2191 QUOTE_IF_REQUIRED(user_, user);
2192 QUOTE_IF_REQUIRED(pass_, pass);
2193 imap_gen_send(session, "LOGIN %s %s", user_, pass_);
2195 ok = imap_cmd_ok(session, NULL);
2196 if (ok != IMAP_SUCCESS)
2197 log_warning(_("IMAP4 login failed.\n"));
2202 static gint imap_cmd_logout(IMAPSession *session)
2204 imap_gen_send(session, "LOGOUT");
2205 return imap_cmd_ok(session, NULL);
2208 /* Send CAPABILITY, and examine the server's response to see whether this
2209 * connection is pre-authenticated or not and build a list of CAPABILITIES. */
2210 static gint imap_greeting(IMAPSession *session)
2215 imap_gen_send(session, "CAPABILITY");
2217 argbuf = g_ptr_array_new();
2219 if (imap_cmd_ok(session, argbuf) != IMAP_SUCCESS ||
2220 ((capstr = search_array_str(argbuf, "CAPABILITY ")) == NULL)) {
2221 ptr_array_free_strings(argbuf);
2222 g_ptr_array_free(argbuf, TRUE);
2226 session->authenticated = search_array_str(argbuf, "PREAUTH") != NULL;
2228 capstr += strlen("CAPABILITY ");
2230 IMAP_SESSION(session)->capability = g_strsplit(capstr, " ", 0);
2232 ptr_array_free_strings(argbuf);
2233 g_ptr_array_free(argbuf, TRUE);
2238 static gboolean imap_has_capability(IMAPSession *session, const gchar *cap)
2242 for (p = session->capability; *p != NULL; ++p)
2243 if (g_strcasecmp(*p, cap) == 0)
2249 void imap_free_capabilities(IMAPSession *session)
2251 g_strfreev(session->capability);
2252 session->capability = NULL;
2255 static const IMAPSet numberlist_to_imapset(MsgNumberList *list)
2257 static GString *imapset = NULL;
2258 static MsgNumberList *numlist, *elem;
2259 guint first, last, next;
2261 if (imapset == NULL)
2262 imapset = g_string_sized_new(256);
2264 g_string_truncate(imapset, 0);
2267 g_slist_free(numlist);
2268 numlist = g_slist_copy(list);
2269 numlist = g_slist_sort(numlist, g_int_compare);
2270 } else if (numlist == NULL) {
2274 first = GPOINTER_TO_INT(numlist->data);
2276 for(elem = g_slist_next(numlist); elem != NULL; elem = g_slist_next(elem)) {
2277 next = GPOINTER_TO_INT(elem->data);
2279 if(next != (last + 1)) {
2280 if (imapset->len > 0)
2281 g_string_append(imapset, ",");
2283 g_string_sprintfa(imapset, "%d", first);
2285 g_string_sprintfa(imapset, "%d:%d", first, last);
2287 if (imapset->len > IMAPCMDLIMIT) {
2297 if (imapset->len > 0)
2298 g_string_append(imapset, ",");
2300 g_string_sprintfa(imapset, "%d", first);
2302 g_string_sprintfa(imapset, "%d:%d", first, last);
2304 g_slist_free(numlist);
2307 MsgNumberList *remaining;
2309 remaining = elem->next;
2310 remaining = g_slist_prepend(remaining, elem->data);
2312 g_slist_free(numlist);
2313 numlist = remaining;
2316 return imapset->str;
2319 static gint imap_cmd_noop(IMAPSession *session)
2321 imap_gen_send(session, "NOOP");
2322 return imap_cmd_ok(session, NULL);
2325 static gint imap_cmd_starttls(IMAPSession *session)
2327 imap_gen_send(session, "STARTTLS");
2328 return imap_cmd_ok(session, NULL);
2331 #define THROW(err) { ok = err; goto catch; }
2333 static gint imap_cmd_namespace(IMAPSession *session, gchar **ns_str)
2339 argbuf = g_ptr_array_new();
2341 imap_gen_send(session, "NAMESPACE");
2342 if ((ok = imap_cmd_ok(session, argbuf)) != IMAP_SUCCESS) THROW(ok);
2344 str = search_array_str(argbuf, "NAMESPACE");
2345 if (!str) THROW(IMAP_ERROR);
2347 *ns_str = g_strdup(str);
2350 ptr_array_free_strings(argbuf);
2351 g_ptr_array_free(argbuf, TRUE);
2358 static gint imap_cmd_list(IMAPSession *session, const gchar *ref,
2359 const gchar *mailbox, GPtrArray *argbuf)
2361 gchar *ref_, *mailbox_;
2363 if (!ref) ref = "\"\"";
2364 if (!mailbox) mailbox = "\"\"";
2366 QUOTE_IF_REQUIRED(ref_, ref);
2367 QUOTE_IF_REQUIRED(mailbox_, mailbox);
2368 imap_gen_send(session, "LIST %s %s", ref_, mailbox_);
2370 return imap_cmd_ok(session, argbuf);
2373 #define THROW goto catch
2375 static gint imap_cmd_do_select(IMAPSession *session, const gchar *folder,
2377 gint *exists, gint *recent, gint *unseen,
2378 guint32 *uid_validity)
2386 *exists = *recent = *unseen = *uid_validity = 0;
2387 argbuf = g_ptr_array_new();
2390 select_cmd = "EXAMINE";
2392 select_cmd = "SELECT";
2394 QUOTE_IF_REQUIRED(folder_, folder);
2395 imap_gen_send(session, "%s %s", select_cmd, folder_);
2397 if ((ok = imap_cmd_ok(session, argbuf)) != IMAP_SUCCESS) THROW;
2399 resp_str = search_array_contain_str(argbuf, "EXISTS");
2401 if (sscanf(resp_str,"%d EXISTS", exists) != 1) {
2402 g_warning("imap_cmd_select(): invalid EXISTS line.\n");
2407 resp_str = search_array_contain_str(argbuf, "RECENT");
2409 if (sscanf(resp_str, "%d RECENT", recent) != 1) {
2410 g_warning("imap_cmd_select(): invalid RECENT line.\n");
2415 resp_str = search_array_contain_str(argbuf, "UIDVALIDITY");
2417 if (sscanf(resp_str, "OK [UIDVALIDITY %u] ", uid_validity)
2419 g_warning("imap_cmd_select(): invalid UIDVALIDITY line.\n");
2424 resp_str = search_array_contain_str(argbuf, "UNSEEN");
2426 if (sscanf(resp_str, "OK [UNSEEN %d] ", unseen) != 1) {
2427 g_warning("imap_cmd_select(): invalid UNSEEN line.\n");
2433 ptr_array_free_strings(argbuf);
2434 g_ptr_array_free(argbuf, TRUE);
2439 static gint imap_cmd_select(IMAPSession *session, const gchar *folder,
2440 gint *exists, gint *recent, gint *unseen,
2441 guint32 *uid_validity)
2443 return imap_cmd_do_select(session, folder, FALSE,
2444 exists, recent, unseen, uid_validity);
2447 static gint imap_cmd_examine(IMAPSession *session, const gchar *folder,
2448 gint *exists, gint *recent, gint *unseen,
2449 guint32 *uid_validity)
2451 return imap_cmd_do_select(session, folder, TRUE,
2452 exists, recent, unseen, uid_validity);
2457 static gint imap_cmd_create(IMAPSession *session, const gchar *folder)
2461 QUOTE_IF_REQUIRED(folder_, folder);
2462 imap_gen_send(session, "CREATE %s", folder_);
2464 return imap_cmd_ok(session, NULL);
2467 static gint imap_cmd_rename(IMAPSession *session, const gchar *old_folder,
2468 const gchar *new_folder)
2470 gchar *old_folder_, *new_folder_;
2472 QUOTE_IF_REQUIRED(old_folder_, old_folder);
2473 QUOTE_IF_REQUIRED(new_folder_, new_folder);
2474 imap_gen_send(session, "RENAME %s %s", old_folder_, new_folder_);
2476 return imap_cmd_ok(session, NULL);
2479 static gint imap_cmd_delete(IMAPSession *session, const gchar *folder)
2483 QUOTE_IF_REQUIRED(folder_, folder);
2484 imap_gen_send(session, "DELETE %s", folder_);
2486 return imap_cmd_ok(session, NULL);
2489 static gint imap_cmd_search(IMAPSession *session, const gchar *criteria, GSList **list)
2495 g_return_val_if_fail(criteria != NULL, IMAP_ERROR);
2496 g_return_val_if_fail(list != NULL, IMAP_ERROR);
2500 argbuf = g_ptr_array_new();
2501 imap_gen_send(session, "UID SEARCH %s", criteria);
2503 ok = imap_cmd_ok(session, argbuf);
2504 if (ok != IMAP_SUCCESS) {
2505 ptr_array_free_strings(argbuf);
2506 g_ptr_array_free(argbuf, TRUE);
2510 if ((uidlist = search_array_str(argbuf, "SEARCH ")) != NULL) {
2511 gchar **strlist, **p;
2513 strlist = g_strsplit(uidlist + 7, " ", 0);
2514 for (p = strlist; *p != NULL; ++p) {
2517 if (sscanf(*p, "%d", &msgnum) == 1)
2518 *list = g_slist_append(*list, GINT_TO_POINTER(msgnum));
2520 g_strfreev(strlist);
2522 ptr_array_free_strings(argbuf);
2523 g_ptr_array_free(argbuf, TRUE);
2525 return IMAP_SUCCESS;
2528 static gint imap_cmd_fetch(IMAPSession *session, guint32 uid, const gchar *filename)
2536 g_return_val_if_fail(filename != NULL, IMAP_ERROR);
2538 imap_gen_send(session, "UID FETCH %d BODY.PEEK[]", uid);
2540 while ((ok = imap_gen_recv(session, &buf))
2542 if (buf[0] != '*' || buf[1] != ' ') {
2546 if (strstr(buf, "FETCH") != NULL)
2549 if (ok != IMAP_SUCCESS)
2552 cur_pos = strchr(buf, '{');
2553 if (cur_pos == NULL) {
2557 cur_pos = strchr_cpy(cur_pos + 1, '}', size_str, sizeof(size_str));
2558 if (cur_pos == NULL) {
2562 size_num = atol(size_str);
2563 g_return_val_if_fail(size_num > 0, IMAP_ERROR);
2565 if (*cur_pos != '\0') {
2570 if (recv_bytes_write_to_file(SESSION(session)->sock, size_num, filename) != 0) {
2575 if (imap_gen_recv(session, &buf) != IMAP_SUCCESS) {
2580 if (buf[0] == '\0' || buf[strlen(buf) - 1] != ')') {
2586 ok = imap_cmd_ok(session, NULL);
2591 static gint imap_cmd_append(IMAPSession *session, const gchar *destfolder,
2592 const gchar *file, gint32 *new_uid)
2597 gchar buf[BUFFSIZE], *imapbuf;
2602 g_return_val_if_fail(file != NULL, IMAP_ERROR);
2604 size = get_file_size_as_crlf(file);
2605 if ((fp = fopen(file, "rb")) == NULL) {
2606 FILE_OP_ERROR(file, "fopen");
2609 QUOTE_IF_REQUIRED(destfolder_, destfolder);
2610 imap_gen_send(session, "APPEND %s (\\Seen) {%d}", destfolder_, size);
2612 ok = imap_gen_recv(session, &imapbuf);
2613 if (ok != IMAP_SUCCESS || imapbuf[0] != '+' || imapbuf[1] != ' ') {
2614 log_warning(_("can't append %s to %s\n"), file, destfolder_);
2621 log_print("IMAP4> %s\n", _("(sending file...)"));
2623 while (fgets(buf, sizeof(buf), fp) != NULL) {
2625 if (sock_puts(SESSION(session)->sock, buf) < 0) {
2632 FILE_OP_ERROR(file, "fgets");
2637 sock_puts(SESSION(session)->sock, "");
2641 reply = g_ptr_array_new();
2644 ok = imap_cmd_ok(session, reply);
2645 if (ok != IMAP_SUCCESS)
2646 log_warning(_("can't append message to %s\n"), destfolder_);
2648 (new_uid != NULL) &&
2649 (imap_has_capability(session, "UIDPLUS") && reply->len > 0) &&
2650 ((okmsginfo = g_ptr_array_index(reply, reply->len - 1)) != NULL) &&
2651 (sscanf(okmsginfo, "%*u OK [APPENDUID %*u %u]", &newuid) == 1)) {
2655 ptr_array_free_strings(reply);
2656 g_ptr_array_free(reply, TRUE);
2661 static gint imap_cmd_copy(IMAPSession * session,
2663 const gchar * destfolder, gint32 * new_uid)
2666 gint32 olduid, newuid;
2671 g_return_val_if_fail(destfolder != NULL, IMAP_ERROR);
2672 g_return_val_if_fail(session != NULL, IMAP_ERROR);
2673 g_return_val_if_fail(new_uid != NULL, IMAP_ERROR);
2675 QUOTE_IF_REQUIRED(destfolder_, destfolder);
2676 imap_gen_send(session, "UID COPY %d %s", msgnum, destfolder_);
2678 reply = g_ptr_array_new();
2681 ok = imap_cmd_ok(session, reply);
2682 if (ok != IMAP_SUCCESS)
2683 log_warning(_("can't copy %d to %s\n"), msgnum, destfolder_);
2684 else if (imap_has_capability(session, "UIDPLUS") && reply->len > 0)
2685 if ((okmsginfo = g_ptr_array_index(reply, reply->len - 1)) != NULL &&
2686 sscanf(okmsginfo, "%*u OK [COPYUID %*u %u %u]", &olduid, &newuid) == 2 &&
2690 ptr_array_free_strings(reply);
2691 g_ptr_array_free(reply, TRUE);
2695 gint imap_cmd_envelope(IMAPSession *session, IMAPSet set)
2697 static GString *header_fields = NULL;
2699 if (header_fields == NULL) {
2700 const HeaderEntry *headers, *elem;
2702 headers = procheader_get_headernames(FALSE);
2703 header_fields = g_string_new("");
2705 for (elem = headers; elem->name != NULL; ++elem) {
2706 gint namelen = strlen(elem->name);
2708 /* Header fields ending with space are not rfc822 headers */
2709 if (elem->name[namelen - 1] == ' ')
2712 /* strip : at the of header field */
2713 if(elem->name[namelen - 1] == ':')
2719 g_string_sprintfa(header_fields, "%s%.*s",
2720 header_fields->str[0] != '\0' ? " " : "",
2721 namelen, elem->name);
2726 (session, "UID FETCH %s (UID FLAGS RFC822.SIZE BODY.PEEK[HEADER.FIELDS (%s)])",
2727 set, header_fields->str);
2729 return IMAP_SUCCESS;
2732 static gint imap_cmd_store(IMAPSession *session, IMAPSet set,
2737 imap_gen_send(session, "UID STORE %s %s",
2740 if ((ok = imap_cmd_ok(session, NULL)) != IMAP_SUCCESS) {
2741 log_warning(_("error while imap command: STORE %s %s\n"),
2746 return IMAP_SUCCESS;
2749 static gint imap_cmd_expunge(IMAPSession *session)
2753 imap_gen_send(session, "EXPUNGE");
2754 if ((ok = imap_cmd_ok(session, NULL)) != IMAP_SUCCESS) {
2755 log_warning(_("error while imap command: EXPUNGE\n"));
2759 return IMAP_SUCCESS;
2762 static gint imap_cmd_ok(IMAPSession *session, GPtrArray *argbuf)
2764 gint ok = IMAP_SUCCESS;
2769 while ((ok = imap_gen_recv(session, &buf))
2771 // make sure data is long enough for any substring of buf
2772 data = alloca(strlen(buf) + 1);
2774 // untagged line read
2775 if (buf[0] == '*' && buf[1] == ' ') {
2778 g_ptr_array_add(argbuf, g_strdup(buf + 2));
2780 if (sscanf(buf + 2, "%d %s", &num, data) >= 2) {
2781 if (!strcmp(data, "EXISTS")) {
2782 session->exists = num;
2783 session->folder_content_changed = TRUE;
2786 if(!strcmp(data, "EXPUNGE")) {
2788 session->folder_content_changed = TRUE;
2791 // tagged line with correct tag and OK response found
2792 } else if ((sscanf(buf, "%d %s", &cmd_num, data) >= 2) &&
2793 (cmd_num == session->cmd_count) &&
2794 !strcmp(data, "OK")) {
2796 g_ptr_array_add(argbuf, g_strdup(buf));
2810 static void imap_gen_send(IMAPSession *session, const gchar *format, ...)
2817 va_start(args, format);
2818 tmp = g_strdup_vprintf(format, args);
2821 session->cmd_count++;
2823 buf = g_strdup_printf("%d %s\r\n", session->cmd_count, tmp);
2824 if (!strncasecmp(tmp, "LOGIN ", 6) && (p = strchr(tmp + 6, ' '))) {
2826 log_print("IMAP4> %d %s ********\n", session->cmd_count, tmp);
2828 log_print("IMAP4> %d %s\n", session->cmd_count, tmp);
2830 sock_write_all(SESSION(session)->sock, buf, strlen(buf));
2835 static gint imap_gen_recv(IMAPSession *session, gchar **buf)
2837 if ((*buf = sock_getline(SESSION(session)->sock)) == NULL)
2842 log_print("IMAP4< %s\n", *buf);
2844 return IMAP_SUCCESS;
2848 /* misc utility functions */
2850 static gchar *strchr_cpy(const gchar *src, gchar ch, gchar *dest, gint len)
2855 tmp = strchr(src, ch);
2859 memcpy(dest, src, MIN(tmp - src, len - 1));
2860 dest[MIN(tmp - src, len - 1)] = '\0';
2865 static gchar *get_quoted(const gchar *src, gchar ch, gchar *dest, gint len)
2867 const gchar *p = src;
2870 g_return_val_if_fail(*p == ch, NULL);
2875 while (*p != '\0' && *p != ch) {
2877 if (*p == '\\' && *(p + 1) != '\0')
2886 return (gchar *)(*p == ch ? p + 1 : p);
2889 static gchar *search_array_contain_str(GPtrArray *array, const gchar *str)
2893 for (i = 0; i < array->len; i++) {
2896 tmp = g_ptr_array_index(array, i);
2897 if (strstr(tmp, str) != NULL)
2904 static gchar *search_array_str(GPtrArray *array, const gchar *str)
2911 for (i = 0; i < array->len; i++) {
2914 tmp = g_ptr_array_index(array, i);
2915 if (!strncmp(tmp, str, len))
2922 static void imap_path_separator_subst(gchar *str, gchar separator)
2925 gboolean in_escape = FALSE;
2927 if (!separator || separator == '/') return;
2929 for (p = str; *p != '\0'; p++) {
2930 if (*p == '/' && !in_escape)
2932 else if (*p == '&' && *(p + 1) != '-' && !in_escape)
2934 else if (*p == '-' && in_escape)
2939 static gchar *imap_modified_utf7_to_locale(const gchar *mutf7_str)
2942 const gchar *from_p;
2945 to = g_malloc(strlen(mutf7_str) + 1);
2948 for (from_p = mutf7_str; *from_p != '\0'; from_p++) {
2949 if (*from_p == '&' && *(from_p + 1) == '-') {
2959 static iconv_t cd = (iconv_t)-1;
2960 static gboolean iconv_ok = TRUE;
2963 size_t norm_utf7_len;
2965 gchar *to_str, *to_p;
2967 gboolean in_escape = FALSE;
2969 if (!iconv_ok) return g_strdup(mutf7_str);
2971 if (cd == (iconv_t)-1) {
2972 cd = iconv_open(conv_get_current_charset_str(), "UTF-7");
2973 if (cd == (iconv_t)-1) {
2974 g_warning("iconv cannot convert UTF-7 to %s\n",
2975 conv_get_current_charset_str());
2977 return g_strdup(mutf7_str);
2981 norm_utf7 = g_string_new(NULL);
2983 for (p = mutf7_str; *p != '\0'; p++) {
2984 /* replace: '&' -> '+',
2986 escaped ',' -> '/' */
2987 if (!in_escape && *p == '&') {
2988 if (*(p + 1) != '-') {
2989 g_string_append_c(norm_utf7, '+');
2992 g_string_append_c(norm_utf7, '&');
2995 } else if (in_escape && *p == ',') {
2996 g_string_append_c(norm_utf7, '/');
2997 } else if (in_escape && *p == '-') {
2998 g_string_append_c(norm_utf7, '-');
3001 g_string_append_c(norm_utf7, *p);
3005 norm_utf7_p = norm_utf7->str;
3006 norm_utf7_len = norm_utf7->len;
3007 to_len = strlen(mutf7_str) * 5;
3008 to_p = to_str = g_malloc(to_len + 1);
3010 if (iconv(cd, (ICONV_CONST gchar **)&norm_utf7_p, &norm_utf7_len,
3011 &to_p, &to_len) == -1) {
3012 g_warning(_("iconv cannot convert UTF-7 to %s\n"),
3013 conv_get_current_charset_str());
3014 g_string_free(norm_utf7, TRUE);
3016 return g_strdup(mutf7_str);
3019 /* second iconv() call for flushing */
3020 iconv(cd, NULL, NULL, &to_p, &to_len);
3021 g_string_free(norm_utf7, TRUE);
3025 #endif /* !HAVE_ICONV */
3028 static gchar *imap_locale_to_modified_utf7(const gchar *from)
3031 const gchar *from_p;
3034 to = g_malloc(strlen(from) * 2 + 1);
3037 for (from_p = from; *from_p != '\0'; from_p++) {
3038 if (*from_p == '&') {
3048 static iconv_t cd = (iconv_t)-1;
3049 static gboolean iconv_ok = TRUE;
3050 gchar *norm_utf7, *norm_utf7_p;
3051 size_t from_len, norm_utf7_len;
3053 gchar *from_tmp, *to, *p;
3054 gboolean in_escape = FALSE;
3056 if (!iconv_ok) return g_strdup(from);
3058 if (cd == (iconv_t)-1) {
3059 cd = iconv_open("UTF-7", conv_get_current_charset_str());
3060 if (cd == (iconv_t)-1) {
3061 g_warning("iconv cannot convert %s to UTF-7\n",
3062 conv_get_current_charset_str());
3064 return g_strdup(from);
3068 Xstrdup_a(from_tmp, from, return g_strdup(from));
3069 from_len = strlen(from);
3070 norm_utf7_len = from_len * 5;
3071 Xalloca(norm_utf7, norm_utf7_len + 1, return g_strdup(from));
3072 norm_utf7_p = norm_utf7;
3074 #define IS_PRINT(ch) (isprint(ch) && isascii(ch))
3076 while (from_len > 0) {
3077 if (*from_tmp == '+') {
3078 *norm_utf7_p++ = '+';
3079 *norm_utf7_p++ = '-';
3083 } else if (IS_PRINT(*from_tmp)) {
3084 /* printable ascii char */
3085 *norm_utf7_p = *from_tmp;
3091 size_t mb_len = 0, conv_len = 0;
3093 /* unprintable char: convert to UTF-7 */
3095 while (!IS_PRINT(*p) && conv_len < from_len) {
3096 mb_len = mblen(p, MB_LEN_MAX);
3098 g_warning("wrong multibyte sequence\n");
3099 return g_strdup(from);
3105 from_len -= conv_len;
3106 if (iconv(cd, (ICONV_CONST gchar **)&from_tmp,
3108 &norm_utf7_p, &norm_utf7_len) == -1) {
3109 g_warning("iconv cannot convert %s to UTF-7\n",
3110 conv_get_current_charset_str());
3111 return g_strdup(from);
3114 /* second iconv() call for flushing */
3115 iconv(cd, NULL, NULL, &norm_utf7_p, &norm_utf7_len);
3121 *norm_utf7_p = '\0';
3122 to_str = g_string_new(NULL);
3123 for (p = norm_utf7; p < norm_utf7_p; p++) {
3124 /* replace: '&' -> "&-",
3127 BASE64 '/' -> ',' */
3128 if (!in_escape && *p == '&') {
3129 g_string_append(to_str, "&-");
3130 } else if (!in_escape && *p == '+') {
3131 if (*(p + 1) == '-') {
3132 g_string_append_c(to_str, '+');
3135 g_string_append_c(to_str, '&');
3138 } else if (in_escape && *p == '/') {
3139 g_string_append_c(to_str, ',');
3140 } else if (in_escape && *p == '-') {
3141 g_string_append_c(to_str, '-');
3144 g_string_append_c(to_str, *p);
3150 g_string_append_c(to_str, '-');
3154 g_string_free(to_str, FALSE);
3157 #endif /* !HAVE_ICONV */
3160 static gboolean imap_rename_folder_func(GNode *node, gpointer data)
3162 FolderItem *item = node->data;
3163 gchar **paths = data;
3164 const gchar *oldpath = paths[0];
3165 const gchar *newpath = paths[1];
3167 gchar *new_itempath;
3170 oldpathlen = strlen(oldpath);
3171 if (strncmp(oldpath, item->path, oldpathlen) != 0) {
3172 g_warning("path doesn't match: %s, %s\n", oldpath, item->path);
3176 base = item->path + oldpathlen;
3177 while (*base == G_DIR_SEPARATOR) base++;
3179 new_itempath = g_strdup(newpath);
3181 new_itempath = g_strconcat(newpath, G_DIR_SEPARATOR_S, base,
3184 item->path = new_itempath;
3189 static gint get_list_of_uids(Folder *folder, IMAPFolderItem *item, GSList **msgnum_list)
3191 gint ok, nummsgs = 0, lastuid_old;
3192 IMAPSession *session;
3193 GSList *uidlist, *elem;
3196 session = imap_session_get(folder);
3197 g_return_val_if_fail(session != NULL, -1);
3199 ok = imap_select(session, IMAP_FOLDER(folder), item->item.path,
3200 NULL, NULL, NULL, NULL);
3201 if (ok != IMAP_SUCCESS)
3204 cmd_buf = g_strdup_printf("UID %d:*", item->lastuid + 1);
3205 ok = imap_cmd_search(session, cmd_buf, &uidlist);
3208 if (ok == IMAP_SOCKET) {
3209 session_destroy((Session *)session);
3210 ((RemoteFolder *)folder)->session = NULL;
3214 if (ok != IMAP_SUCCESS) {
3218 argbuf = g_ptr_array_new();
3220 cmd_buf = g_strdup_printf("UID FETCH %d:* (UID)", item->lastuid + 1);
3221 imap_gen_send(session, cmd_buf);
3223 ok = imap_cmd_ok(session, argbuf);
3224 if (ok != IMAP_SUCCESS) {
3225 ptr_array_free_strings(argbuf);
3226 g_ptr_array_free(argbuf, TRUE);
3230 for(i = 0; i < argbuf->len; i++) {
3233 if((ret = sscanf(g_ptr_array_index(argbuf, i),
3234 "%*d FETCH (UID %d)", &msgnum)) == 1)
3235 uidlist = g_slist_prepend(uidlist, GINT_TO_POINTER(msgnum));
3237 ptr_array_free_strings(argbuf);
3238 g_ptr_array_free(argbuf, TRUE);
3241 lastuid_old = item->lastuid;
3242 *msgnum_list = g_slist_copy(item->uid_list);
3243 nummsgs = g_slist_length(*msgnum_list);
3244 debug_print("Got %d uids from cache\n", g_slist_length(item->uid_list));
3246 for (elem = uidlist; elem != NULL; elem = g_slist_next(elem)) {
3249 msgnum = GPOINTER_TO_INT(elem->data);
3250 if (msgnum > lastuid_old) {
3251 *msgnum_list = g_slist_prepend(*msgnum_list, GINT_TO_POINTER(msgnum));
3252 item->uid_list = g_slist_prepend(item->uid_list, GINT_TO_POINTER(msgnum));
3255 if(msgnum > item->lastuid)
3256 item->lastuid = msgnum;
3259 g_slist_free(uidlist);
3264 gint imap_get_num_list(Folder *folder, FolderItem *_item, GSList **msgnum_list)
3266 IMAPFolderItem *item = (IMAPFolderItem *)_item;
3267 IMAPSession *session;
3268 gint ok, nummsgs = 0, exists, recent, unseen, uid_val, uid_next;
3271 gboolean selected_folder;
3273 g_return_val_if_fail(folder != NULL, -1);
3274 g_return_val_if_fail(item != NULL, -1);
3275 g_return_val_if_fail(item->item.path != NULL, -1);
3276 g_return_val_if_fail(FOLDER_CLASS(folder) == &imap_class, -1);
3277 g_return_val_if_fail(folder->account != NULL, -1);
3279 session = imap_session_get(folder);
3280 g_return_val_if_fail(session != NULL, -1);
3282 selected_folder = (session->mbox != NULL) &&
3283 (!strcmp(session->mbox, item->item.path));
3284 if (selected_folder) {
3285 ok = imap_cmd_noop(session);
3286 if (ok != IMAP_SUCCESS)
3288 exists = session->exists;
3290 ok = imap_status(session, IMAP_FOLDER(folder), item->item.path,
3291 &exists, &recent, &uid_next, &uid_val, &unseen);
3292 if (ok != IMAP_SUCCESS)
3296 /* If old uid_next matches new uid_next we can be sure no message
3297 was added to the folder */
3298 if (( selected_folder && !session->folder_content_changed) ||
3299 (!selected_folder && uid_next == item->uid_next)) {
3300 nummsgs = g_slist_length(item->uid_list);
3302 /* If number of messages is still the same we
3303 know our caches message numbers are still valid,
3304 otherwise if the number of messages has decrease
3305 we discard our cache to start a new scan to find
3306 out which numbers have been removed */
3307 if (exists == nummsgs) {
3308 *msgnum_list = g_slist_copy(item->uid_list);
3310 } else if (exists < nummsgs) {
3311 debug_print("Freeing imap uid cache");
3313 g_slist_free(item->uid_list);
3314 item->uid_list = NULL;
3317 if (!selected_folder)
3318 item->uid_next = uid_next;
3321 *msgnum_list = NULL;
3325 nummsgs = get_list_of_uids(folder, item, &uidlist);
3327 if (nummsgs != exists) {
3328 /* Cache contains more messages then folder, we have cached
3329 an old UID of a message that was removed and new messages
3330 have been added too, otherwise the uid_next check would
3332 debug_print("Freeing imap uid cache");
3334 g_slist_free(item->uid_list);
3335 item->uid_list = NULL;
3337 g_slist_free(*msgnum_list);
3339 nummsgs = get_list_of_uids(folder, item, &uidlist);
3342 *msgnum_list = uidlist;
3344 dir = folder_item_get_path((FolderItem *)item);
3345 debug_print("removing old messages from %s\n", dir);
3346 remove_numbered_files_not_in_list(dir, *msgnum_list);
3352 static MsgInfo *imap_parse_msg(const gchar *file, FolderItem *item)
3357 flags.perm_flags = MSG_NEW|MSG_UNREAD;
3358 flags.tmp_flags = 0;
3360 g_return_val_if_fail(item != NULL, NULL);
3361 g_return_val_if_fail(file != NULL, NULL);
3363 if (item->stype == F_QUEUE) {
3364 MSG_SET_TMP_FLAGS(flags, MSG_QUEUED);
3365 } else if (item->stype == F_DRAFT) {
3366 MSG_SET_TMP_FLAGS(flags, MSG_DRAFT);
3369 msginfo = procheader_parse_file(file, flags, FALSE, FALSE);
3370 if (!msginfo) return NULL;
3372 msginfo->folder = item;
3377 GSList *imap_get_msginfos(Folder *folder, FolderItem *item, GSList *msgnum_list)
3379 IMAPSession *session;
3380 MsgInfoList *ret = NULL;
3383 g_return_val_if_fail(folder != NULL, NULL);
3384 g_return_val_if_fail(item != NULL, NULL);
3385 g_return_val_if_fail(msgnum_list != NULL, NULL);
3387 session = imap_session_get(folder);
3388 g_return_val_if_fail(session != NULL, NULL);
3390 ok = imap_select(session, IMAP_FOLDER(folder), item->path,
3391 NULL, NULL, NULL, NULL);
3392 if (ok != IMAP_SUCCESS)
3395 if (!(item->stype == F_QUEUE || item->stype == F_DRAFT)) {
3396 ret = g_slist_concat(ret,
3397 imap_get_uncached_messages(
3398 session, item, msgnum_list));
3400 MsgNumberList *sorted_list, *elem;
3401 gint startnum, lastnum;
3403 sorted_list = g_slist_sort(g_slist_copy(msgnum_list), g_int_compare);
3405 startnum = lastnum = GPOINTER_TO_INT(sorted_list->data);
3407 for (elem = sorted_list;; elem = g_slist_next(elem)) {
3411 num = GPOINTER_TO_INT(elem->data);
3413 if (num > lastnum + 1 || elem == NULL) {
3415 for (i = startnum; i <= lastnum; ++i) {
3418 file = imap_fetch_msg(folder, item, i);
3420 MsgInfo *msginfo = imap_parse_msg(file, item);
3421 if (msginfo != NULL) {
3422 msginfo->msgnum = i;
3423 ret = g_slist_append(ret, msginfo);
3437 g_slist_free(sorted_list);
3443 MsgInfo *imap_get_msginfo(Folder *folder, FolderItem *item, gint uid)
3445 MsgInfo *msginfo = NULL;
3446 MsgInfoList *msginfolist;
3447 MsgNumberList numlist;
3449 numlist.next = NULL;
3450 numlist.data = GINT_TO_POINTER(uid);
3452 msginfolist = imap_get_msginfos(folder, item, &numlist);
3453 if (msginfolist != NULL) {
3454 msginfo = msginfolist->data;
3455 g_slist_free(msginfolist);
3461 gboolean imap_check_msgnum_validity(Folder *folder, FolderItem *_item)
3463 IMAPSession *session;
3464 IMAPFolderItem *item = (IMAPFolderItem *)_item;
3465 gint ok, exists = 0, recent = 0, unseen = 0;
3466 guint32 uid_next, uid_validity = 0;
3468 g_return_val_if_fail(folder != NULL, FALSE);
3469 g_return_val_if_fail(item != NULL, FALSE);
3470 g_return_val_if_fail(item->item.folder != NULL, FALSE);
3471 g_return_val_if_fail(FOLDER_CLASS(item->item.folder) == &imap_class, FALSE);
3473 session = imap_session_get(folder);
3474 g_return_val_if_fail(session != NULL, FALSE);
3476 ok = imap_status(session, IMAP_FOLDER(folder), item->item.path,
3477 &exists, &recent, &uid_next, &uid_validity, &unseen);
3478 if (ok != IMAP_SUCCESS)
3481 if(item->item.mtime == uid_validity)
3484 debug_print("Freeing imap uid cache\n");
3486 g_slist_free(item->uid_list);
3487 item->uid_list = NULL;
3489 item->item.mtime = uid_validity;
3491 imap_delete_all_cached_messages((FolderItem *)item);
3496 void imap_change_flags(Folder *folder, FolderItem *item, MsgInfo *msginfo, MsgPermFlags newflags)
3498 IMAPSession *session;
3499 IMAPFlags flags_set = 0, flags_unset = 0;
3500 gint ok = IMAP_SUCCESS;
3501 MsgNumberList numlist;
3503 g_return_if_fail(folder != NULL);
3504 g_return_if_fail(folder->klass == &imap_class);
3505 g_return_if_fail(item != NULL);
3506 g_return_if_fail(item->folder == folder);
3507 g_return_if_fail(msginfo != NULL);
3508 g_return_if_fail(msginfo->folder == item);
3510 session = imap_session_get(folder);
3511 if (!session) return;
3513 if ((ok = imap_select(session, IMAP_FOLDER(folder), msginfo->folder->path,
3514 NULL, NULL, NULL, NULL)) != IMAP_SUCCESS)
3517 if (!MSG_IS_MARKED(msginfo->flags) && (newflags & MSG_MARKED))
3518 flags_set |= IMAP_FLAG_FLAGGED;
3519 if ( MSG_IS_MARKED(msginfo->flags) && !(newflags & MSG_MARKED))
3520 flags_unset |= IMAP_FLAG_FLAGGED;
3522 if (!MSG_IS_UNREAD(msginfo->flags) && (newflags & MSG_UNREAD))
3523 flags_unset |= IMAP_FLAG_SEEN;
3524 if ( MSG_IS_UNREAD(msginfo->flags) && !(newflags & MSG_UNREAD))
3525 flags_set |= IMAP_FLAG_SEEN;
3527 if (!MSG_IS_REPLIED(msginfo->flags) && (newflags & MSG_REPLIED))
3528 flags_set |= IMAP_FLAG_ANSWERED;
3529 if ( MSG_IS_REPLIED(msginfo->flags) && !(newflags & MSG_REPLIED))
3530 flags_set |= IMAP_FLAG_ANSWERED;
3532 numlist.next = NULL;
3533 numlist.data = GINT_TO_POINTER(msginfo->msgnum);
3536 ok = imap_set_message_flags(session, &numlist, flags_set, TRUE);
3537 if (ok != IMAP_SUCCESS) return;
3541 ok = imap_set_message_flags(session, &numlist, flags_unset, FALSE);
3542 if (ok != IMAP_SUCCESS) return;
3545 msginfo->flags.perm_flags = newflags;