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
107 IMAP_FLAG_SEEN = 1 << 0,
108 IMAP_FLAG_ANSWERED = 1 << 1,
109 IMAP_FLAG_FLAGGED = 1 << 2,
110 IMAP_FLAG_DELETED = 1 << 3,
111 IMAP_FLAG_DRAFT = 1 << 4
114 #define IMAP_IS_SEEN(flags) ((flags & IMAP_FLAG_SEEN) != 0)
115 #define IMAP_IS_ANSWERED(flags) ((flags & IMAP_FLAG_ANSWERED) != 0)
116 #define IMAP_IS_FLAGGED(flags) ((flags & IMAP_FLAG_FLAGGED) != 0)
117 #define IMAP_IS_DELETED(flags) ((flags & IMAP_FLAG_DELETED) != 0)
118 #define IMAP_IS_DRAFT(flags) ((flags & IMAP_FLAG_DRAFT) != 0)
121 #define IMAP4_PORT 143
123 #define IMAPS_PORT 993
126 #define QUOTE_IF_REQUIRED(out, str) \
128 if (*str != '"' && strpbrk(str, " \t(){}%*") != NULL) { \
132 len = strlen(str) + 3; \
133 Xalloca(__tmp, len, return IMAP_ERROR); \
134 g_snprintf(__tmp, len, "\"%s\"", str); \
137 Xstrdup_a(out, str, return IMAP_ERROR); \
141 typedef gchar * IMAPSet;
143 struct _IMAPFolderItem
152 static Folder *imap_folder_new(const gchar * name, const gchar * path);
153 static void imap_folder_destroy(Folder * folder);
155 static IMAPSession *imap_session_new(const PrefsAccount * account);
156 static void imap_session_authenticate(IMAPSession * session,
157 const PrefsAccount * account);
158 static void imap_session_destroy(Session * session);
160 static gchar *imap_fetch_msg(Folder * folder, FolderItem * item, gint uid);
161 static gint imap_add_msg(Folder * folder,
163 const gchar * file, gboolean remove_source);
165 static gint imap_copy_msg(Folder * folder,
166 FolderItem * dest, MsgInfo * msginfo);
168 static gint imap_remove_msg(Folder * folder, FolderItem * item, gint uid);
169 static gint imap_remove_all_msg(Folder * folder, FolderItem * item);
171 static gboolean imap_is_msg_changed(Folder * folder,
172 FolderItem * item, MsgInfo * msginfo);
174 static gint imap_scan_folder(Folder * folder, FolderItem * item);
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 (Folder *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(MsgNumberList *list);
287 /* low-level IMAP4rev1 commands */
288 static gint imap_cmd_login (IMAPSession *sock,
291 static gint imap_cmd_logout (IMAPSession *sock);
292 static gint imap_cmd_noop (IMAPSession *sock);
293 static gint imap_cmd_starttls (IMAPSession *sock);
294 static gint imap_cmd_namespace (IMAPSession *sock,
296 static gint imap_cmd_list (IMAPSession *session,
298 const gchar *mailbox,
300 static gint imap_cmd_do_select (IMAPSession *sock,
306 guint32 *uid_validity);
307 static gint imap_cmd_select (IMAPSession *sock,
312 guint32 *uid_validity);
313 static gint imap_cmd_examine (IMAPSession *sock,
318 guint32 *uid_validity);
319 static gint imap_cmd_create (IMAPSession *sock,
320 const gchar *folder);
321 static gint imap_cmd_rename (IMAPSession *sock,
322 const gchar *oldfolder,
323 const gchar *newfolder);
324 static gint imap_cmd_delete (IMAPSession *sock,
325 const gchar *folder);
326 static gint imap_cmd_envelope (IMAPSession *sock,
328 static gint imap_cmd_fetch (IMAPSession *sock,
330 const gchar *filename);
331 static gint imap_cmd_append (IMAPSession *session,
332 const gchar *destfolder,
335 static gint imap_cmd_copy (IMAPSession *session,
337 const gchar *destfolder,
339 static gint imap_cmd_store (IMAPSession *sock,
342 static gint imap_cmd_expunge (IMAPSession *sock);
344 static gint imap_cmd_ok (IMAPSession *session,
346 static void imap_gen_send (IMAPSession *sock,
347 const gchar *format, ...);
348 static gint imap_gen_recv (IMAPSession *sock,
351 /* misc utility functions */
352 static gchar *strchr_cpy (const gchar *src,
356 static gchar *get_quoted (const gchar *src,
360 static gchar *search_array_contain_str (GPtrArray *array,
362 static gchar *search_array_str (GPtrArray *array,
364 static void imap_path_separator_subst (gchar *str,
367 static gchar *imap_modified_utf7_to_locale (const gchar *mutf7_str);
368 static gchar *imap_locale_to_modified_utf7 (const gchar *from);
370 static gboolean imap_rename_folder_func (GNode *node,
372 static gint imap_get_num_list (Folder *folder,
375 static GSList *imap_get_msginfos (Folder *folder,
377 GSList *msgnum_list);
378 static MsgInfo *imap_get_msginfo (Folder *folder,
381 static gboolean imap_check_msgnum_validity (Folder *folder,
383 static void imap_change_flags (Folder *folder,
386 MsgPermFlags newflags);
388 FolderClass imap_class =
394 /* Folder functions */
400 /* FolderItem functions */
401 imap_folder_item_new,
402 imap_folder_item_destroy,
410 imap_check_msgnum_validity,
412 /* Message functions */
424 FolderClass *imap_get_class(void)
429 Folder *imap_folder_new(const gchar *name, const gchar *path)
433 folder = (Folder *)g_new0(IMAPFolder, 1);
434 folder->klass = &imap_class;
435 imap_folder_init(folder, name, path);
440 void imap_folder_destroy(Folder *folder)
444 dir = folder_get_path(folder);
445 if (is_dir_exist(dir))
446 remove_dir_recursive(dir);
449 folder_remote_folder_destroy(REMOTE_FOLDER(folder));
452 static void imap_folder_init(Folder *folder, const gchar *name,
455 folder_remote_folder_init((Folder *)folder, name, path);
458 static FolderItem *imap_folder_item_new(Folder *folder)
460 IMAPFolderItem *item;
462 item = g_new0(IMAPFolderItem, 1);
465 item->uid_list = NULL;
467 return (FolderItem *)item;
470 static void imap_folder_item_destroy(Folder *folder, FolderItem *_item)
472 IMAPFolderItem *item = (IMAPFolderItem *)_item;
474 g_return_if_fail(item != NULL);
475 g_slist_free(item->uid_list);
480 static gboolean imap_reset_uid_lists_func(GNode *node, gpointer data)
482 IMAPFolderItem *item = (IMAPFolderItem *)node->data;
486 g_slist_free(item->uid_list);
487 item->uid_list = NULL;
492 static void imap_reset_uid_lists(Folder *folder)
494 if(folder->node == NULL)
497 /* Destroy all uid lists and rest last uid */
498 g_node_traverse(folder->node, G_IN_ORDER, G_TRAVERSE_ALL, -1, imap_reset_uid_lists_func, NULL);
501 static IMAPSession *imap_session_get(Folder *folder)
503 RemoteFolder *rfolder = REMOTE_FOLDER(folder);
504 IMAPSession *session = NULL;
507 g_return_val_if_fail(folder != NULL, NULL);
508 g_return_val_if_fail(FOLDER_CLASS(folder) == &imap_class, NULL);
509 g_return_val_if_fail(folder->account != NULL, NULL);
512 port = folder->account->set_imapport ? folder->account->imapport
513 : folder->account->ssl_imap == SSL_TUNNEL
514 ? IMAPS_PORT : IMAP4_PORT;
516 port = folder->account->set_imapport ? folder->account->imapport
520 /* Make sure we have a session */
521 if (rfolder->session != NULL) {
522 session = IMAP_SESSION(rfolder->session);
524 imap_reset_uid_lists(folder);
525 session = imap_session_new(folder->account);
530 if (SESSION(session)->sock->state == CONN_DISCONNECTED) {
531 debug_print("IMAP server disconnected\n");
532 session_destroy(SESSION(session));
533 imap_reset_uid_lists(folder);
534 session = imap_session_new(folder->account);
537 /* Make sure session is authenticated */
538 if (!IMAP_SESSION(session)->authenticated)
539 imap_session_authenticate(IMAP_SESSION(session), folder->account);
540 if (!IMAP_SESSION(session)->authenticated) {
541 session_destroy(SESSION(session));
542 rfolder->session = NULL;
546 /* Make sure we have parsed the IMAP namespace */
547 imap_parse_namespace(IMAP_SESSION(session),
548 IMAP_FOLDER(folder));
550 /* I think the point of this code is to avoid sending a
551 * keepalive if we've used the session recently and therefore
552 * think it's still alive. Unfortunately, most of the code
553 * does not yet check for errors on the socket, and so if the
554 * connection drops we don't notice until the timeout expires.
555 * A better solution than sending a NOOP every time would be
556 * for every command to be prepared to retry until it is
557 * successfully sent. -- mbp */
558 if (time(NULL) - session->last_access_time > SESSION_TIMEOUT) {
559 /* verify that the session is still alive */
560 if (imap_cmd_noop(session) != IMAP_SUCCESS) {
561 /* Check if this is the first try to establish a
562 connection, if yes we don't try to reconnect */
563 if (rfolder->session == NULL) {
564 log_warning(_("Connecting %s:%d failed"),
565 folder->account->recv_server, port);
566 session_destroy(SESSION(session));
569 log_warning(_("IMAP4 connection to %s:%d has been"
570 " disconnected. Reconnecting...\n"),
571 folder->account->recv_server, port);
572 session_destroy(SESSION(session));
573 /* Clear folders session to make imap_session_get create
574 a new session, because of rfolder->session == NULL
575 it will not try to reconnect again and so avoid an
577 rfolder->session = NULL;
578 session = imap_session_get(folder);
583 rfolder->session = SESSION(session);
585 session->last_access_time = time(NULL);
587 return IMAP_SESSION(session);
590 IMAPSession *imap_session_new(const PrefsAccount *account)
592 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 = is_preauth;
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 = is_preauth;
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 (is_preauth) ? "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 gint imap_scan_folder(Folder *folder, FolderItem *item)
973 IMAPSession *session;
974 gint messages, recent, unseen;
975 guint32 uid_next, uid_validity;
978 g_return_val_if_fail(folder != NULL, -1);
979 g_return_val_if_fail(item != NULL, -1);
981 session = imap_session_get(folder);
982 if (!session) return -1;
984 ok = imap_status(session, IMAP_FOLDER(folder), item->path,
985 &messages, &recent, &uid_next, &uid_validity, &unseen);
986 if (ok != IMAP_SUCCESS) return -1;
988 item->new_msgs = unseen > 0 ? recent : 0;
989 item->unread_msgs = unseen;
990 item->total_msgs = messages;
991 item->last_num = (messages > 0 && uid_next > 0) ? uid_next - 1 : 0;
992 /* item->mtime = uid_validity; */
997 void imap_scan_tree(Folder *folder)
1000 IMAPSession *session;
1001 gchar *root_folder = NULL;
1003 g_return_if_fail(folder != NULL);
1004 g_return_if_fail(folder->account != NULL);
1006 session = imap_session_get(folder);
1008 if (!folder->node) {
1009 folder_tree_destroy(folder);
1010 item = folder_item_new(folder, folder->name, NULL);
1011 item->folder = folder;
1012 folder->node = g_node_new(item);
1017 if (folder->account->imap_dir && *folder->account->imap_dir) {
1018 Xstrdup_a(root_folder, folder->account->imap_dir, return);
1019 strtailchomp(root_folder, '/');
1020 debug_print("IMAP root directory: %s\n", root_folder);
1023 item = folder_item_new(folder, folder->name, root_folder);
1024 item->folder = folder;
1025 item->no_select = TRUE;
1026 folder->node = g_node_new(item);
1028 imap_scan_tree_recursive(session, item);
1030 imap_create_missing_folders(folder);
1033 static gint imap_scan_tree_recursive(IMAPSession *session, FolderItem *item)
1036 IMAPFolder *imapfolder;
1037 FolderItem *new_item;
1038 GSList *item_list, *cur;
1040 gchar *wildcard_path;
1044 g_return_val_if_fail(item != NULL, -1);
1045 g_return_val_if_fail(item->folder != NULL, -1);
1046 g_return_val_if_fail(item->no_sub == FALSE, -1);
1048 folder = FOLDER(item->folder);
1049 imapfolder = IMAP_FOLDER(folder);
1051 separator = imap_get_path_separator(imapfolder, item->path);
1053 if (item->folder->ui_func)
1054 item->folder->ui_func(folder, item, folder->ui_func_data);
1057 wildcard[0] = separator;
1060 real_path = imap_get_real_path(imapfolder, item->path);
1064 real_path = g_strdup("");
1067 Xstrcat_a(wildcard_path, real_path, wildcard,
1068 {g_free(real_path); return IMAP_ERROR;});
1069 QUOTE_IF_REQUIRED(wildcard_path, wildcard_path);
1071 imap_gen_send(session, "LIST \"\" %s",
1074 strtailchomp(real_path, separator);
1075 item_list = imap_parse_list(folder, session, real_path, NULL);
1078 for (cur = item_list; cur != NULL; cur = cur->next) {
1079 new_item = cur->data;
1080 if (!strcmp(new_item->path, "INBOX")) {
1081 if (!folder->inbox) {
1082 new_item->stype = F_INBOX;
1083 item->folder->inbox = new_item;
1085 folder_item_destroy(new_item);
1088 } else if (!item->parent || item->stype == F_INBOX) {
1091 base = g_basename(new_item->path);
1093 if (!folder->outbox && !strcasecmp(base, "Sent")) {
1094 new_item->stype = F_OUTBOX;
1095 folder->outbox = new_item;
1096 } else if (!folder->draft && !strcasecmp(base, "Drafts")) {
1097 new_item->stype = F_DRAFT;
1098 folder->draft = new_item;
1099 } else if (!folder->queue && !strcasecmp(base, "Queue")) {
1100 new_item->stype = F_QUEUE;
1101 folder->queue = new_item;
1102 } else if (!folder->trash && !strcasecmp(base, "Trash")) {
1103 new_item->stype = F_TRASH;
1104 folder->trash = new_item;
1107 folder_item_append(item, new_item);
1108 if (new_item->no_select == FALSE)
1109 imap_scan_folder(folder, new_item);
1110 if (new_item->no_sub == FALSE)
1111 imap_scan_tree_recursive(session, new_item);
1114 return IMAP_SUCCESS;
1117 static GSList *imap_parse_list(Folder *folder, IMAPSession *session,
1118 const gchar *real_path, gchar *separator)
1120 gchar buf[IMAPBUFSIZE];
1122 gchar separator_str[16];
1125 gchar *loc_name, *loc_path;
1126 GSList *item_list = NULL;
1128 FolderItem *new_item;
1130 debug_print("getting list of %s ...\n",
1131 *real_path ? real_path : "\"\"");
1133 str = g_string_new(NULL);
1136 if (sock_gets(SESSION(session)->sock, buf, sizeof(buf)) <= 0) {
1137 log_warning(_("error occurred while getting LIST.\n"));
1141 if (buf[0] != '*' || buf[1] != ' ') {
1142 log_print("IMAP4< %s\n", buf);
1145 debug_print("IMAP4< %s\n", buf);
1147 g_string_assign(str, buf);
1149 if (strncmp(p, "LIST ", 5) != 0) continue;
1152 if (*p != '(') continue;
1154 p = strchr_cpy(p, ')', flags, sizeof(flags));
1156 while (*p == ' ') p++;
1158 p = strchr_cpy(p, ' ', separator_str, sizeof(separator_str));
1160 extract_quote(separator_str, '"');
1161 if (!strcmp(separator_str, "NIL"))
1162 separator_str[0] = '\0';
1164 *separator = separator_str[0];
1167 while (*p == ' ') p++;
1168 if (*p == '{' || *p == '"')
1169 p = imap_parse_atom(SESSION(session)->sock, p,
1170 buf, sizeof(buf), str);
1172 strncpy2(buf, p, sizeof(buf));
1173 strtailchomp(buf, separator_str[0]);
1174 if (buf[0] == '\0') continue;
1175 if (!strcmp(buf, real_path)) continue;
1177 if (separator_str[0] != '\0')
1178 subst_char(buf, separator_str[0], '/');
1179 name = g_basename(buf);
1180 if (name[0] == '.') continue;
1182 loc_name = imap_modified_utf7_to_locale(name);
1183 loc_path = imap_modified_utf7_to_locale(buf);
1184 new_item = folder_item_new(folder, loc_name, loc_path);
1185 if (strcasestr(flags, "\\Noinferiors") != NULL)
1186 new_item->no_sub = TRUE;
1187 if (strcmp(buf, "INBOX") != 0 &&
1188 strcasestr(flags, "\\Noselect") != NULL)
1189 new_item->no_select = TRUE;
1191 item_list = g_slist_append(item_list, new_item);
1193 debug_print("folder %s has been added.\n", loc_path);
1198 g_string_free(str, TRUE);
1203 gint imap_create_tree(Folder *folder)
1205 g_return_val_if_fail(folder != NULL, -1);
1206 g_return_val_if_fail(folder->node != NULL, -1);
1207 g_return_val_if_fail(folder->node->data != NULL, -1);
1208 g_return_val_if_fail(folder->account != NULL, -1);
1210 imap_scan_tree(folder);
1211 imap_create_missing_folders(folder);
1216 static void imap_create_missing_folders(Folder *folder)
1218 g_return_if_fail(folder != NULL);
1221 folder->inbox = imap_create_special_folder
1222 (folder, F_INBOX, "INBOX");
1224 if (!folder->outbox)
1225 folder->outbox = imap_create_special_folder
1226 (folder, F_OUTBOX, "Sent");
1228 folder->draft = imap_create_special_folder
1229 (folder, F_DRAFT, "Drafts");
1231 folder->queue = imap_create_special_folder
1232 (folder, F_QUEUE, "Queue");
1235 folder->trash = imap_create_special_folder
1236 (folder, F_TRASH, "Trash");
1239 static FolderItem *imap_create_special_folder(Folder *folder,
1240 SpecialFolderItemType stype,
1244 FolderItem *new_item;
1246 g_return_val_if_fail(folder != NULL, NULL);
1247 g_return_val_if_fail(folder->node != NULL, NULL);
1248 g_return_val_if_fail(folder->node->data != NULL, NULL);
1249 g_return_val_if_fail(folder->account != NULL, NULL);
1250 g_return_val_if_fail(name != NULL, NULL);
1252 item = FOLDER_ITEM(folder->node->data);
1253 new_item = imap_create_folder(folder, item, name);
1256 g_warning("Can't create '%s'\n", name);
1257 if (!folder->inbox) return NULL;
1259 new_item = imap_create_folder(folder, folder->inbox, name);
1261 g_warning("Can't create '%s' under INBOX\n", name);
1263 new_item->stype = stype;
1265 new_item->stype = stype;
1270 FolderItem *imap_create_folder(Folder *folder, FolderItem *parent,
1273 gchar *dirpath, *imap_path;
1274 IMAPSession *session;
1275 FolderItem *new_item;
1281 g_return_val_if_fail(folder != NULL, NULL);
1282 g_return_val_if_fail(folder->account != NULL, NULL);
1283 g_return_val_if_fail(parent != NULL, NULL);
1284 g_return_val_if_fail(name != NULL, NULL);
1286 session = imap_session_get(folder);
1287 if (!session) return NULL;
1289 if (!parent->parent && strcmp(name, "INBOX") == 0)
1290 dirpath = g_strdup(name);
1291 else if (parent->path)
1292 dirpath = g_strconcat(parent->path, "/", name, NULL);
1293 else if ((p = strchr(name, '/')) != NULL && *(p + 1) != '\0')
1294 dirpath = g_strdup(name);
1295 else if (folder->account->imap_dir && *folder->account->imap_dir) {
1298 Xstrdup_a(imap_dir, folder->account->imap_dir, return NULL);
1299 strtailchomp(imap_dir, '/');
1300 dirpath = g_strconcat(imap_dir, "/", name, NULL);
1302 dirpath = g_strdup(name);
1304 /* keep trailing directory separator to create a folder that contains
1306 imap_path = imap_locale_to_modified_utf7(dirpath);
1307 strtailchomp(dirpath, '/');
1308 Xstrdup_a(new_name, name, {g_free(dirpath); return NULL;});
1309 strtailchomp(new_name, '/');
1310 separator = imap_get_path_separator(IMAP_FOLDER(folder), imap_path);
1311 imap_path_separator_subst(imap_path, separator);
1312 subst_char(new_name, '/', separator);
1314 if (strcmp(name, "INBOX") != 0) {
1317 gboolean exist = FALSE;
1319 argbuf = g_ptr_array_new();
1320 ok = imap_cmd_list(session, NULL, imap_path,
1322 if (ok != IMAP_SUCCESS) {
1323 log_warning(_("can't create mailbox: LIST failed\n"));
1326 ptr_array_free_strings(argbuf);
1327 g_ptr_array_free(argbuf, TRUE);
1331 for (i = 0; i < argbuf->len; i++) {
1333 str = g_ptr_array_index(argbuf, i);
1334 if (!strncmp(str, "LIST ", 5)) {
1339 ptr_array_free_strings(argbuf);
1340 g_ptr_array_free(argbuf, TRUE);
1343 ok = imap_cmd_create(session, imap_path);
1344 if (ok != IMAP_SUCCESS) {
1345 log_warning(_("can't create mailbox\n"));
1353 new_item = folder_item_new(folder, new_name, dirpath);
1354 folder_item_append(parent, new_item);
1358 dirpath = folder_item_get_path(new_item);
1359 if (!is_dir_exist(dirpath))
1360 make_dir_hier(dirpath);
1366 gint imap_rename_folder(Folder *folder, FolderItem *item, const gchar *name)
1370 gchar *real_oldpath;
1371 gchar *real_newpath;
1374 gchar *old_cache_dir;
1375 gchar *new_cache_dir;
1376 IMAPSession *session;
1379 gint exists, recent, unseen;
1380 guint32 uid_validity;
1382 g_return_val_if_fail(folder != NULL, -1);
1383 g_return_val_if_fail(item != NULL, -1);
1384 g_return_val_if_fail(item->path != NULL, -1);
1385 g_return_val_if_fail(name != NULL, -1);
1387 session = imap_session_get(folder);
1388 if (!session) return -1;
1390 real_oldpath = imap_get_real_path(IMAP_FOLDER(folder), item->path);
1392 g_free(session->mbox);
1393 session->mbox = NULL;
1394 ok = imap_cmd_examine(session, "INBOX",
1395 &exists, &recent, &unseen, &uid_validity);
1396 if (ok != IMAP_SUCCESS) {
1397 g_free(real_oldpath);
1401 separator = imap_get_path_separator(IMAP_FOLDER(folder), item->path);
1402 if (strchr(item->path, G_DIR_SEPARATOR)) {
1403 dirpath = g_dirname(item->path);
1404 newpath = g_strconcat(dirpath, G_DIR_SEPARATOR_S, name, NULL);
1407 newpath = g_strdup(name);
1409 real_newpath = imap_locale_to_modified_utf7(newpath);
1410 imap_path_separator_subst(real_newpath, separator);
1412 ok = imap_cmd_rename(session, real_oldpath, real_newpath);
1413 if (ok != IMAP_SUCCESS) {
1414 log_warning(_("can't rename mailbox: %s to %s\n"),
1415 real_oldpath, real_newpath);
1416 g_free(real_oldpath);
1418 g_free(real_newpath);
1423 item->name = g_strdup(name);
1425 old_cache_dir = folder_item_get_path(item);
1427 node = g_node_find(item->folder->node, G_PRE_ORDER, G_TRAVERSE_ALL,
1429 paths[0] = g_strdup(item->path);
1431 g_node_traverse(node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
1432 imap_rename_folder_func, paths);
1434 if (is_dir_exist(old_cache_dir)) {
1435 new_cache_dir = folder_item_get_path(item);
1436 if (rename(old_cache_dir, new_cache_dir) < 0) {
1437 FILE_OP_ERROR(old_cache_dir, "rename");
1439 g_free(new_cache_dir);
1442 g_free(old_cache_dir);
1445 g_free(real_oldpath);
1446 g_free(real_newpath);
1451 gint imap_remove_folder(Folder *folder, FolderItem *item)
1454 IMAPSession *session;
1457 gint exists, recent, unseen;
1458 guint32 uid_validity;
1460 g_return_val_if_fail(folder != NULL, -1);
1461 g_return_val_if_fail(item != NULL, -1);
1462 g_return_val_if_fail(item->path != NULL, -1);
1464 session = imap_session_get(folder);
1465 if (!session) return -1;
1467 path = imap_get_real_path(IMAP_FOLDER(folder), item->path);
1469 ok = imap_cmd_examine(session, "INBOX",
1470 &exists, &recent, &unseen, &uid_validity);
1471 if (ok != IMAP_SUCCESS) {
1476 ok = imap_cmd_delete(session, path);
1477 if (ok != IMAP_SUCCESS) {
1478 log_warning(_("can't delete mailbox\n"));
1484 cache_dir = folder_item_get_path(item);
1485 if (is_dir_exist(cache_dir) && remove_dir_recursive(cache_dir) < 0)
1486 g_warning("can't remove directory '%s'\n", cache_dir);
1488 folder_item_remove(item);
1493 static GSList *imap_get_uncached_messages(IMAPSession *session,
1495 MsgNumberList *numlist)
1498 GSList *newlist = NULL;
1499 GSList *llast = NULL;
1503 g_return_val_if_fail(session != NULL, NULL);
1504 g_return_val_if_fail(item != NULL, NULL);
1505 g_return_val_if_fail(item->folder != NULL, NULL);
1506 g_return_val_if_fail(FOLDER_CLASS(item->folder) == &imap_class, NULL);
1508 if (imap_cmd_envelope(session, numberlist_to_imapset(numlist))
1510 log_warning(_("can't get envelope\n"));
1514 str = g_string_new(NULL);
1517 if ((tmp = sock_getline(SESSION(session)->sock)) == NULL) {
1518 log_warning(_("error occurred while getting envelope.\n"));
1519 g_string_free(str, TRUE);
1523 if (tmp[0] != '*' || tmp[1] != ' ') {
1524 log_print("IMAP4< %s\n", tmp);
1528 if (strstr(tmp, "FETCH") == NULL) {
1529 log_print("IMAP4< %s\n", tmp);
1533 log_print("IMAP4< %s\n", tmp);
1534 g_string_assign(str, tmp);
1537 msginfo = imap_parse_envelope
1538 (SESSION(session)->sock, item, str);
1540 log_warning(_("can't parse envelope: %s\n"), str->str);
1543 if (item->stype == F_QUEUE) {
1544 MSG_SET_TMP_FLAGS(msginfo->flags, MSG_QUEUED);
1545 } else if (item->stype == F_DRAFT) {
1546 MSG_SET_TMP_FLAGS(msginfo->flags, MSG_DRAFT);
1549 msginfo->folder = item;
1552 llast = newlist = g_slist_append(newlist, msginfo);
1554 llast = g_slist_append(llast, msginfo);
1555 llast = llast->next;
1559 g_string_free(str, TRUE);
1564 static void imap_delete_all_cached_messages(FolderItem *item)
1568 g_return_if_fail(item != NULL);
1569 g_return_if_fail(item->folder != NULL);
1570 g_return_if_fail(FOLDER_CLASS(item->folder) == &imap_class);
1572 debug_print("Deleting all cached messages...\n");
1574 dir = folder_item_get_path(item);
1575 if (is_dir_exist(dir))
1576 remove_all_numbered_files(dir);
1579 debug_print("done.\n");
1583 static SockInfo *imap_open_tunnel(const gchar *server,
1584 const gchar *tunnelcmd,
1587 static SockInfo *imap_open_tunnel(const gchar *server,
1588 const gchar *tunnelcmd)
1593 if ((sock = sock_connect_cmd(server, tunnelcmd)) == NULL) {
1594 log_warning(_("Can't establish IMAP4 session with: %s\n"),
1599 return imap_init_sock(sock, ssl_type);
1601 return imap_init_sock(sock);
1607 static SockInfo *imap_open(const gchar *server, gushort port,
1610 static SockInfo *imap_open(const gchar *server, gushort port)
1615 if ((sock = sock_connect(server, port)) == NULL) {
1616 log_warning(_("Can't connect to IMAP4 server: %s:%d\n"),
1622 if (ssl_type == SSL_TUNNEL && !ssl_init_socket(sock)) {
1623 log_warning(_("Can't establish IMAP4 session with: %s:%d\n"),
1633 static SockInfo *imap_init_sock(SockInfo *sock, SSLType ssl_type)
1635 static SockInfo *imap_init_sock(SockInfo *sock)
1642 static GList *imap_parse_namespace_str(gchar *str)
1647 IMAPNameSpace *namespace;
1648 GList *ns_list = NULL;
1650 while (*p != '\0') {
1651 /* parse ("#foo" "/") */
1653 while (*p && *p != '(') p++;
1654 if (*p == '\0') break;
1657 while (*p && *p != '"') p++;
1658 if (*p == '\0') break;
1662 while (*p && *p != '"') p++;
1663 if (*p == '\0') break;
1667 while (*p && isspace(*p)) p++;
1668 if (*p == '\0') break;
1669 if (strncmp(p, "NIL", 3) == 0)
1671 else if (*p == '"') {
1674 while (*p && *p != '"') p++;
1675 if (*p == '\0') break;
1680 while (*p && *p != ')') p++;
1681 if (*p == '\0') break;
1684 namespace = g_new(IMAPNameSpace, 1);
1685 namespace->name = g_strdup(name);
1686 namespace->separator = separator ? separator[0] : '\0';
1687 ns_list = g_list_append(ns_list, namespace);
1693 static void imap_parse_namespace(IMAPSession *session, IMAPFolder *folder)
1698 g_return_if_fail(session != NULL);
1699 g_return_if_fail(folder != NULL);
1701 if (folder->ns_personal != NULL ||
1702 folder->ns_others != NULL ||
1703 folder->ns_shared != NULL)
1706 if (!imap_has_capability(session, "NAMESPACE")) {
1707 imap_get_namespace_by_list(session, folder);
1711 if (imap_cmd_namespace(session, &ns_str)
1713 log_warning(_("can't get namespace\n"));
1717 str_array = strsplit_parenthesis(ns_str, '(', ')', 3);
1718 if (str_array == NULL) {
1720 imap_get_namespace_by_list(session, folder);
1724 folder->ns_personal = imap_parse_namespace_str(str_array[0]);
1725 if (str_array[0] && str_array[1])
1726 folder->ns_others = imap_parse_namespace_str(str_array[1]);
1727 if (str_array[0] && str_array[1] && str_array[2])
1728 folder->ns_shared = imap_parse_namespace_str(str_array[2]);
1729 g_strfreev(str_array);
1733 static void imap_get_namespace_by_list(IMAPSession *session, IMAPFolder *folder)
1735 GSList *item_list, *cur;
1736 gchar separator = '\0';
1737 IMAPNameSpace *namespace;
1739 g_return_if_fail(session != NULL);
1740 g_return_if_fail(folder != NULL);
1742 if (folder->ns_personal != NULL ||
1743 folder->ns_others != NULL ||
1744 folder->ns_shared != NULL)
1747 imap_gen_send(session, "LIST \"\" \"\"");
1748 item_list = imap_parse_list(NULL, session, "", &separator);
1749 for (cur = item_list; cur != NULL; cur = cur->next)
1750 folder_item_destroy(FOLDER_ITEM(cur->data));
1751 g_slist_free(item_list);
1753 namespace = g_new(IMAPNameSpace, 1);
1754 namespace->name = g_strdup("");
1755 namespace->separator = separator;
1756 folder->ns_personal = g_list_append(NULL, namespace);
1759 static IMAPNameSpace *imap_find_namespace_from_list(GList *ns_list,
1762 IMAPNameSpace *namespace = NULL;
1763 gchar *tmp_path, *name;
1765 if (!path) path = "";
1767 Xstrcat_a(tmp_path, path, "/", return NULL);
1769 for (; ns_list != NULL; ns_list = ns_list->next) {
1770 IMAPNameSpace *tmp_ns = ns_list->data;
1772 Xstrdup_a(name, tmp_ns->name, return namespace);
1773 if (tmp_ns->separator && tmp_ns->separator != '/')
1774 subst_char(name, tmp_ns->separator, '/');
1775 if (strncmp(tmp_path, name, strlen(name)) == 0)
1782 static IMAPNameSpace *imap_find_namespace(IMAPFolder *folder,
1785 IMAPNameSpace *namespace;
1787 g_return_val_if_fail(folder != NULL, NULL);
1789 namespace = imap_find_namespace_from_list(folder->ns_personal, path);
1790 if (namespace) return namespace;
1791 namespace = imap_find_namespace_from_list(folder->ns_others, path);
1792 if (namespace) return namespace;
1793 namespace = imap_find_namespace_from_list(folder->ns_shared, path);
1794 if (namespace) return namespace;
1799 static gchar imap_get_path_separator(IMAPFolder *folder, const gchar *path)
1801 IMAPNameSpace *namespace;
1802 gchar separator = '/';
1804 namespace = imap_find_namespace(folder, path);
1805 if (namespace && namespace->separator)
1806 separator = namespace->separator;
1811 static gchar *imap_get_real_path(IMAPFolder *folder, const gchar *path)
1816 g_return_val_if_fail(folder != NULL, NULL);
1817 g_return_val_if_fail(path != NULL, NULL);
1819 real_path = imap_locale_to_modified_utf7(path);
1820 separator = imap_get_path_separator(folder, path);
1821 imap_path_separator_subst(real_path, separator);
1826 static gchar *imap_parse_atom(SockInfo *sock, gchar *src,
1827 gchar *dest, gint dest_len, GString *str)
1829 gchar *cur_pos = src;
1832 g_return_val_if_fail(str != NULL, cur_pos);
1834 /* read the next line if the current response buffer is empty */
1835 while (isspace(*cur_pos)) cur_pos++;
1836 while (*cur_pos == '\0') {
1837 if ((nextline = sock_getline(sock)) == NULL)
1839 g_string_assign(str, nextline);
1841 strretchomp(nextline);
1842 /* log_print("IMAP4< %s\n", nextline); */
1843 debug_print("IMAP4< %s\n", nextline);
1846 while (isspace(*cur_pos)) cur_pos++;
1849 if (!strncmp(cur_pos, "NIL", 3)) {
1852 } else if (*cur_pos == '\"') {
1855 p = get_quoted(cur_pos, '\"', dest, dest_len);
1856 cur_pos = p ? p : cur_pos + 2;
1857 } else if (*cur_pos == '{') {
1862 cur_pos = strchr_cpy(cur_pos + 1, '}', buf, sizeof(buf));
1864 g_return_val_if_fail(len > 0, cur_pos);
1866 g_string_truncate(str, 0);
1870 if ((nextline = sock_getline(sock)) == NULL)
1872 line_len += strlen(nextline);
1873 g_string_append(str, nextline);
1875 strretchomp(nextline);
1876 /* log_print("IMAP4< %s\n", nextline); */
1877 debug_print("IMAP4< %s\n", nextline);
1879 } while (line_len < len);
1881 memcpy(dest, cur_pos, MIN(len, dest_len - 1));
1882 dest[MIN(len, dest_len - 1)] = '\0';
1889 static gchar *imap_get_header(SockInfo *sock, gchar *cur_pos, gchar **headers,
1899 g_return_val_if_fail(str != NULL, cur_pos);
1901 while (isspace(*cur_pos)) cur_pos++;
1903 g_return_val_if_fail(*cur_pos == '{', cur_pos);
1905 cur_pos = strchr_cpy(cur_pos + 1, '}', buf, sizeof(buf));
1907 g_return_val_if_fail(len > 0, cur_pos);
1909 g_string_truncate(str, 0);
1913 if ((nextline = sock_getline(sock)) == NULL)
1915 block_len += strlen(nextline);
1916 g_string_append(str, nextline);
1918 strretchomp(nextline);
1919 /* debug_print("IMAP4< %s\n", nextline); */
1921 } while (block_len < len);
1923 debug_print("IMAP4< [contents of BODY.PEEK[HEADER.FIELDS (...)]]\n");
1925 *headers = g_strndup(cur_pos, len);
1928 while (isspace(*cur_pos)) cur_pos++;
1929 while (*cur_pos == '\0') {
1930 if ((nextline = sock_getline(sock)) == NULL)
1932 g_string_assign(str, nextline);
1934 strretchomp(nextline);
1935 debug_print("IMAP4< %s\n", nextline);
1938 while (isspace(*cur_pos)) cur_pos++;
1944 static MsgFlags imap_parse_flags(const gchar *flag_str)
1946 const gchar *p = flag_str;
1947 MsgFlags flags = {0, 0};
1949 flags.perm_flags = MSG_UNREAD;
1951 while ((p = strchr(p, '\\')) != NULL) {
1954 if (g_strncasecmp(p, "Recent", 6) == 0 && MSG_IS_UNREAD(flags)) {
1955 MSG_SET_PERM_FLAGS(flags, MSG_NEW);
1956 } else if (g_strncasecmp(p, "Seen", 4) == 0) {
1957 MSG_UNSET_PERM_FLAGS(flags, MSG_NEW|MSG_UNREAD);
1958 } else if (g_strncasecmp(p, "Deleted", 7) == 0) {
1959 MSG_SET_PERM_FLAGS(flags, MSG_DELETED);
1960 } else if (g_strncasecmp(p, "Flagged", 7) == 0) {
1961 MSG_SET_PERM_FLAGS(flags, MSG_MARKED);
1962 } else if (g_strncasecmp(p, "Answered", 8) == 0) {
1963 MSG_SET_PERM_FLAGS(flags, MSG_REPLIED);
1970 static MsgInfo *imap_parse_envelope(SockInfo *sock, FolderItem *item,
1973 gchar buf[IMAPBUFSIZE];
1974 MsgInfo *msginfo = NULL;
1979 MsgFlags flags = {0, 0}, imap_flags = {0, 0};
1981 g_return_val_if_fail(line_str != NULL, NULL);
1982 g_return_val_if_fail(line_str->str[0] == '*' &&
1983 line_str->str[1] == ' ', NULL);
1985 MSG_SET_TMP_FLAGS(flags, MSG_IMAP);
1986 if (item->stype == F_QUEUE) {
1987 MSG_SET_TMP_FLAGS(flags, MSG_QUEUED);
1988 } else if (item->stype == F_DRAFT) {
1989 MSG_SET_TMP_FLAGS(flags, MSG_DRAFT);
1992 cur_pos = line_str->str + 2;
1994 #define PARSE_ONE_ELEMENT(ch) \
1996 cur_pos = strchr_cpy(cur_pos, ch, buf, sizeof(buf)); \
1997 if (cur_pos == NULL) { \
1998 g_warning("cur_pos == NULL\n"); \
1999 procmsg_msginfo_free(msginfo); \
2004 PARSE_ONE_ELEMENT(' ');
2007 PARSE_ONE_ELEMENT(' ');
2008 g_return_val_if_fail(!strcmp(buf, "FETCH"), NULL);
2010 g_return_val_if_fail(*cur_pos == '(', NULL);
2013 while (*cur_pos != '\0' && *cur_pos != ')') {
2014 while (*cur_pos == ' ') cur_pos++;
2016 if (!strncmp(cur_pos, "UID ", 4)) {
2018 uid = strtoul(cur_pos, &cur_pos, 10);
2019 } else if (!strncmp(cur_pos, "FLAGS ", 6)) {
2021 if (*cur_pos != '(') {
2022 g_warning("*cur_pos != '('\n");
2023 procmsg_msginfo_free(msginfo);
2027 PARSE_ONE_ELEMENT(')');
2028 imap_flags = imap_parse_flags(buf);
2029 } else if (!strncmp(cur_pos, "RFC822.SIZE ", 12)) {
2031 size = strtol(cur_pos, &cur_pos, 10);
2032 } else if (!strncmp(cur_pos, "BODY[HEADER.FIELDS ", 19)) {
2036 if (*cur_pos != '(') {
2037 g_warning("*cur_pos != '('\n");
2038 procmsg_msginfo_free(msginfo);
2042 PARSE_ONE_ELEMENT(')');
2043 if (*cur_pos != ']') {
2044 g_warning("*cur_pos != ']'\n");
2045 procmsg_msginfo_free(msginfo);
2050 cur_pos = imap_get_header(sock, cur_pos, &headers,
2052 msginfo = procheader_parse_str(headers, flags, FALSE, FALSE);
2055 g_warning("invalid FETCH response: %s\n", cur_pos);
2061 msginfo->msgnum = uid;
2062 msginfo->size = size;
2063 msginfo->flags.tmp_flags |= imap_flags.tmp_flags;
2064 msginfo->flags.perm_flags = imap_flags.perm_flags;
2070 static gint imap_set_message_flags(IMAPSession *session,
2071 MsgNumberList *numlist,
2078 buf = g_string_new(is_set ? "+FLAGS (" : "-FLAGS (");
2080 if (IMAP_IS_SEEN(flags)) g_string_append(buf, "\\Seen ");
2081 if (IMAP_IS_ANSWERED(flags)) g_string_append(buf, "\\Answered ");
2082 if (IMAP_IS_FLAGGED(flags)) g_string_append(buf, "\\Flagged ");
2083 if (IMAP_IS_DELETED(flags)) g_string_append(buf, "\\Deleted ");
2084 if (IMAP_IS_DRAFT(flags)) g_string_append(buf, "\\Draft");
2086 if (buf->str[buf->len - 1] == ' ')
2087 g_string_truncate(buf, buf->len - 1);
2089 g_string_append_c(buf, ')');
2091 ok = imap_cmd_store(session, numberlist_to_imapset(numlist),
2093 g_string_free(buf, TRUE);
2098 static gint imap_select(IMAPSession *session, IMAPFolder *folder,
2100 gint *exists, gint *recent, gint *unseen,
2101 guint32 *uid_validity)
2105 gint exists_, recent_, unseen_, uid_validity_;
2107 if (!exists || !recent || !unseen || !uid_validity) {
2108 if (session->mbox && strcmp(session->mbox, path) == 0)
2109 return IMAP_SUCCESS;
2113 uid_validity = &uid_validity_;
2116 g_free(session->mbox);
2117 session->mbox = NULL;
2119 real_path = imap_get_real_path(folder, path);
2120 ok = imap_cmd_select(session, real_path,
2121 exists, recent, unseen, uid_validity);
2122 if (ok != IMAP_SUCCESS)
2123 log_warning(_("can't select folder: %s\n"), real_path);
2125 session->mbox = g_strdup(path);
2126 session->folder_content_changed = FALSE;
2133 #define THROW(err) { ok = err; goto catch; }
2135 static gint imap_status(IMAPSession *session, IMAPFolder *folder,
2137 gint *messages, gint *recent,
2138 guint32 *uid_next, guint32 *uid_validity,
2147 *messages = *recent = *uid_next = *uid_validity = *unseen = 0;
2149 argbuf = g_ptr_array_new();
2151 real_path = imap_get_real_path(folder, path);
2152 QUOTE_IF_REQUIRED(real_path_, real_path);
2153 imap_gen_send(session, "STATUS %s "
2154 "(MESSAGES RECENT UIDNEXT UIDVALIDITY UNSEEN)",
2157 ok = imap_cmd_ok(session, argbuf);
2158 if (ok != IMAP_SUCCESS) THROW(ok);
2160 str = search_array_str(argbuf, "STATUS");
2161 if (!str) THROW(IMAP_ERROR);
2163 str = strchr(str, '(');
2164 if (!str) THROW(IMAP_ERROR);
2166 while (*str != '\0' && *str != ')') {
2167 while (*str == ' ') str++;
2169 if (!strncmp(str, "MESSAGES ", 9)) {
2171 *messages = strtol(str, &str, 10);
2172 } else if (!strncmp(str, "RECENT ", 7)) {
2174 *recent = strtol(str, &str, 10);
2175 } else if (!strncmp(str, "UIDNEXT ", 8)) {
2177 *uid_next = strtoul(str, &str, 10);
2178 } else if (!strncmp(str, "UIDVALIDITY ", 12)) {
2180 *uid_validity = strtoul(str, &str, 10);
2181 } else if (!strncmp(str, "UNSEEN ", 7)) {
2183 *unseen = strtol(str, &str, 10);
2185 g_warning("invalid STATUS response: %s\n", str);
2192 ptr_array_free_strings(argbuf);
2193 g_ptr_array_free(argbuf, TRUE);
2201 /* low-level IMAP4rev1 commands */
2203 static gint imap_cmd_login(IMAPSession *session,
2204 const gchar *user, const gchar *pass)
2206 gchar *user_, *pass_;
2209 QUOTE_IF_REQUIRED(user_, user);
2210 QUOTE_IF_REQUIRED(pass_, pass);
2211 imap_gen_send(session, "LOGIN %s %s", user_, pass_);
2213 ok = imap_cmd_ok(session, NULL);
2214 if (ok != IMAP_SUCCESS)
2215 log_warning(_("IMAP4 login failed.\n"));
2220 static gint imap_cmd_logout(IMAPSession *session)
2222 imap_gen_send(session, "LOGOUT");
2223 return imap_cmd_ok(session, NULL);
2226 /* Send CAPABILITY, and examine the server's response to see whether this
2227 * connection is pre-authenticated or not and build a list of CAPABILITIES. */
2228 static gint imap_greeting(IMAPSession *session)
2233 imap_gen_send(session, "CAPABILITY");
2235 argbuf = g_ptr_array_new();
2237 if (imap_cmd_ok(session, argbuf) != IMAP_SUCCESS ||
2238 ((capstr = search_array_str(argbuf, "CAPABILITY ")) == NULL)) {
2239 ptr_array_free_strings(argbuf);
2240 g_ptr_array_free(argbuf, TRUE);
2244 session->authenticated = search_array_str(argbuf, "PREAUTH") != NULL;
2246 capstr += strlen("CAPABILITY ");
2248 IMAP_SESSION(session)->capability = g_strsplit(capstr, " ", 0);
2250 ptr_array_free_strings(argbuf);
2251 g_ptr_array_free(argbuf, TRUE);
2256 static gboolean imap_has_capability(IMAPSession *session, const gchar *cap)
2260 for (p = session->capability; *p != NULL; ++p)
2261 if (g_strcasecmp(*p, cap) == 0)
2267 void imap_free_capabilities(IMAPSession *session)
2269 g_strfreev(session->capability);
2270 session->capability = NULL;
2273 static const IMAPSet numberlist_to_imapset(MsgNumberList *list)
2275 static GString *imapset = NULL;
2276 MsgNumberList *numlist, *elem;
2277 guint first, last, next;
2279 if (imapset == NULL)
2280 imapset = g_string_sized_new(256);
2282 g_string_truncate(imapset, 0);
2284 numlist = g_slist_copy(list);
2285 numlist = g_slist_sort(numlist, g_int_compare);
2287 first = GPOINTER_TO_INT(numlist->data);
2289 for(elem = g_slist_next(numlist); elem != NULL; elem = g_slist_next(elem)) {
2290 next = GPOINTER_TO_INT(elem->data);
2291 if(next != (last + 1)) {
2292 if (imapset->len > 0)
2293 g_string_append(imapset, ",");
2295 g_string_sprintfa(imapset, "%d", first);
2297 g_string_sprintfa(imapset, "%d:%d", first, last);
2303 if (imapset->len > 0)
2304 g_string_append(imapset, ",");
2306 g_string_sprintfa(imapset, "%d", first);
2308 g_string_sprintfa(imapset, "%d:%d", first, last);
2310 g_slist_free(numlist);
2312 return imapset->str;
2315 static gint imap_cmd_noop(IMAPSession *session)
2317 imap_gen_send(session, "NOOP");
2318 return imap_cmd_ok(session, NULL);
2321 static gint imap_cmd_starttls(IMAPSession *session)
2323 imap_gen_send(session, "STARTTLS");
2324 return imap_cmd_ok(session, NULL);
2327 #define THROW(err) { ok = err; goto catch; }
2329 static gint imap_cmd_namespace(IMAPSession *session, gchar **ns_str)
2335 argbuf = g_ptr_array_new();
2337 imap_gen_send(session, "NAMESPACE");
2338 if ((ok = imap_cmd_ok(session, argbuf)) != IMAP_SUCCESS) THROW(ok);
2340 str = search_array_str(argbuf, "NAMESPACE");
2341 if (!str) THROW(IMAP_ERROR);
2343 *ns_str = g_strdup(str);
2346 ptr_array_free_strings(argbuf);
2347 g_ptr_array_free(argbuf, TRUE);
2354 static gint imap_cmd_list(IMAPSession *session, const gchar *ref,
2355 const gchar *mailbox, GPtrArray *argbuf)
2357 gchar *ref_, *mailbox_;
2359 if (!ref) ref = "\"\"";
2360 if (!mailbox) mailbox = "\"\"";
2362 QUOTE_IF_REQUIRED(ref_, ref);
2363 QUOTE_IF_REQUIRED(mailbox_, mailbox);
2364 imap_gen_send(session, "LIST %s %s", ref_, mailbox_);
2366 return imap_cmd_ok(session, argbuf);
2369 #define THROW goto catch
2371 static gint imap_cmd_do_select(IMAPSession *session, const gchar *folder,
2373 gint *exists, gint *recent, gint *unseen,
2374 guint32 *uid_validity)
2382 *exists = *recent = *unseen = *uid_validity = 0;
2383 argbuf = g_ptr_array_new();
2386 select_cmd = "EXAMINE";
2388 select_cmd = "SELECT";
2390 QUOTE_IF_REQUIRED(folder_, folder);
2391 imap_gen_send(session, "%s %s", select_cmd, folder_);
2393 if ((ok = imap_cmd_ok(session, argbuf)) != IMAP_SUCCESS) THROW;
2395 resp_str = search_array_contain_str(argbuf, "EXISTS");
2397 if (sscanf(resp_str,"%d EXISTS", exists) != 1) {
2398 g_warning("imap_cmd_select(): invalid EXISTS line.\n");
2403 resp_str = search_array_contain_str(argbuf, "RECENT");
2405 if (sscanf(resp_str, "%d RECENT", recent) != 1) {
2406 g_warning("imap_cmd_select(): invalid RECENT line.\n");
2411 resp_str = search_array_contain_str(argbuf, "UIDVALIDITY");
2413 if (sscanf(resp_str, "OK [UIDVALIDITY %u] ", uid_validity)
2415 g_warning("imap_cmd_select(): invalid UIDVALIDITY line.\n");
2420 resp_str = search_array_contain_str(argbuf, "UNSEEN");
2422 if (sscanf(resp_str, "OK [UNSEEN %d] ", unseen) != 1) {
2423 g_warning("imap_cmd_select(): invalid UNSEEN line.\n");
2429 ptr_array_free_strings(argbuf);
2430 g_ptr_array_free(argbuf, TRUE);
2435 static gint imap_cmd_select(IMAPSession *session, const gchar *folder,
2436 gint *exists, gint *recent, gint *unseen,
2437 guint32 *uid_validity)
2439 return imap_cmd_do_select(session, folder, FALSE,
2440 exists, recent, unseen, uid_validity);
2443 static gint imap_cmd_examine(IMAPSession *session, const gchar *folder,
2444 gint *exists, gint *recent, gint *unseen,
2445 guint32 *uid_validity)
2447 return imap_cmd_do_select(session, folder, TRUE,
2448 exists, recent, unseen, uid_validity);
2453 static gint imap_cmd_create(IMAPSession *session, const gchar *folder)
2457 QUOTE_IF_REQUIRED(folder_, folder);
2458 imap_gen_send(session, "CREATE %s", folder_);
2460 return imap_cmd_ok(session, NULL);
2463 static gint imap_cmd_rename(IMAPSession *session, const gchar *old_folder,
2464 const gchar *new_folder)
2466 gchar *old_folder_, *new_folder_;
2468 QUOTE_IF_REQUIRED(old_folder_, old_folder);
2469 QUOTE_IF_REQUIRED(new_folder_, new_folder);
2470 imap_gen_send(session, "RENAME %s %s", old_folder_, new_folder_);
2472 return imap_cmd_ok(session, NULL);
2475 static gint imap_cmd_delete(IMAPSession *session, const gchar *folder)
2479 QUOTE_IF_REQUIRED(folder_, folder);
2480 imap_gen_send(session, "DELETE %s", folder_);
2482 return imap_cmd_ok(session, NULL);
2485 static gint imap_cmd_search(IMAPSession *session, const gchar *criteria, GSList **list)
2491 g_return_val_if_fail(criteria != NULL, IMAP_ERROR);
2492 g_return_val_if_fail(list != NULL, IMAP_ERROR);
2496 argbuf = g_ptr_array_new();
2497 imap_gen_send(session, "UID SEARCH %s", criteria);
2499 ok = imap_cmd_ok(session, argbuf);
2500 if (ok != IMAP_SUCCESS) {
2501 ptr_array_free_strings(argbuf);
2502 g_ptr_array_free(argbuf, TRUE);
2506 if ((uidlist = search_array_str(argbuf, "SEARCH ")) != NULL) {
2507 gchar **strlist, **p;
2509 strlist = g_strsplit(uidlist + 7, " ", 0);
2510 for (p = strlist; *p != NULL; ++p) {
2513 if (sscanf(*p, "%d", &msgnum) == 1)
2514 *list = g_slist_append(*list, GINT_TO_POINTER(msgnum));
2516 g_strfreev(strlist);
2518 ptr_array_free_strings(argbuf);
2519 g_ptr_array_free(argbuf, TRUE);
2521 return IMAP_SUCCESS;
2524 static gint imap_cmd_fetch(IMAPSession *session, guint32 uid, const gchar *filename)
2532 g_return_val_if_fail(filename != NULL, IMAP_ERROR);
2534 imap_gen_send(session, "UID FETCH %d BODY.PEEK[]", uid);
2536 while ((ok = imap_gen_recv(session, &buf))
2538 if (buf[0] != '*' || buf[1] != ' ') {
2542 if (strstr(buf, "FETCH") != NULL)
2545 if (ok != IMAP_SUCCESS)
2548 cur_pos = strchr(buf, '{');
2549 if (cur_pos == NULL) {
2553 cur_pos = strchr_cpy(cur_pos + 1, '}', size_str, sizeof(size_str));
2554 if (cur_pos == NULL) {
2558 size_num = atol(size_str);
2559 g_return_val_if_fail(size_num > 0, IMAP_ERROR);
2561 if (*cur_pos != '\0') {
2566 if (recv_bytes_write_to_file(SESSION(session)->sock, size_num, filename) != 0) {
2571 if (imap_gen_recv(session, &buf) != IMAP_SUCCESS) {
2576 if (buf[0] == '\0' || buf[strlen(buf) - 1] != ')') {
2582 ok = imap_cmd_ok(session, NULL);
2587 static gint imap_cmd_append(IMAPSession *session, const gchar *destfolder,
2588 const gchar *file, gint32 *new_uid)
2593 gchar buf[BUFFSIZE], *imapbuf;
2598 g_return_val_if_fail(file != NULL, IMAP_ERROR);
2600 size = get_file_size_as_crlf(file);
2601 if ((fp = fopen(file, "rb")) == NULL) {
2602 FILE_OP_ERROR(file, "fopen");
2605 QUOTE_IF_REQUIRED(destfolder_, destfolder);
2606 imap_gen_send(session, "APPEND %s (\\Seen) {%d}", destfolder_, size);
2608 ok = imap_gen_recv(session, &imapbuf);
2609 if (ok != IMAP_SUCCESS || imapbuf[0] != '+' || imapbuf[1] != ' ') {
2610 log_warning(_("can't append %s to %s\n"), file, destfolder_);
2617 log_print("IMAP4> %s\n", _("(sending file...)"));
2619 while (fgets(buf, sizeof(buf), fp) != NULL) {
2621 if (sock_puts(SESSION(session)->sock, buf) < 0) {
2628 FILE_OP_ERROR(file, "fgets");
2633 sock_puts(SESSION(session)->sock, "");
2637 reply = g_ptr_array_new();
2640 ok = imap_cmd_ok(session, reply);
2641 if (ok != IMAP_SUCCESS)
2642 log_warning(_("can't append message to %s\n"), destfolder_);
2644 (new_uid != NULL) &&
2645 (imap_has_capability(session, "UIDPLUS") && reply->len > 0) &&
2646 ((okmsginfo = g_ptr_array_index(reply, reply->len - 1)) != NULL) &&
2647 (sscanf(okmsginfo, "%*u OK [APPENDUID %*u %u]", &newuid) == 1)) {
2651 ptr_array_free_strings(reply);
2652 g_ptr_array_free(reply, TRUE);
2657 static gint imap_cmd_copy(IMAPSession * session,
2659 const gchar * destfolder, gint32 * new_uid)
2662 gint32 olduid, newuid;
2667 g_return_val_if_fail(destfolder != NULL, IMAP_ERROR);
2668 g_return_val_if_fail(session != NULL, IMAP_ERROR);
2669 g_return_val_if_fail(new_uid != NULL, IMAP_ERROR);
2671 QUOTE_IF_REQUIRED(destfolder_, destfolder);
2672 imap_gen_send(session, "UID COPY %d %s", msgnum, destfolder_);
2674 reply = g_ptr_array_new();
2677 ok = imap_cmd_ok(session, reply);
2678 if (ok != IMAP_SUCCESS)
2679 log_warning(_("can't copy %d to %s\n"), msgnum, destfolder_);
2680 else if (imap_has_capability(session, "UIDPLUS") && reply->len > 0)
2681 if ((okmsginfo = g_ptr_array_index(reply, reply->len - 1)) != NULL &&
2682 sscanf(okmsginfo, "%*u OK [COPYUID %*u %u %u]", &olduid, &newuid) == 2 &&
2686 ptr_array_free_strings(reply);
2687 g_ptr_array_free(reply, TRUE);
2691 gint imap_cmd_envelope(IMAPSession *session, IMAPSet set)
2693 static GString *header_fields = NULL;
2695 if (header_fields == NULL) {
2696 const HeaderEntry *headers, *elem;
2698 headers = procheader_get_headernames(FALSE);
2699 header_fields = g_string_new("");
2701 for (elem = headers; elem->name != NULL; ++elem) {
2702 gint namelen = strlen(elem->name);
2704 /* Header fields ending with space are not rfc822 headers */
2705 if (elem->name[namelen - 1] == ' ')
2708 /* strip : at the of header field */
2709 if(elem->name[namelen - 1] == ':')
2715 g_string_sprintfa(header_fields, "%s%.*s",
2716 header_fields->str[0] != '\0' ? " " : "",
2717 namelen, elem->name);
2722 (session, "UID FETCH %s (UID FLAGS RFC822.SIZE BODY.PEEK[HEADER.FIELDS (%s)])",
2723 set, header_fields->str);
2725 return IMAP_SUCCESS;
2728 static gint imap_cmd_store(IMAPSession *session, IMAPSet set,
2733 imap_gen_send(session, "UID STORE %s %s",
2736 if ((ok = imap_cmd_ok(session, NULL)) != IMAP_SUCCESS) {
2737 log_warning(_("error while imap command: STORE %s %s\n"),
2742 return IMAP_SUCCESS;
2745 static gint imap_cmd_expunge(IMAPSession *session)
2749 imap_gen_send(session, "EXPUNGE");
2750 if ((ok = imap_cmd_ok(session, NULL)) != IMAP_SUCCESS) {
2751 log_warning(_("error while imap command: EXPUNGE\n"));
2755 return IMAP_SUCCESS;
2758 static gint imap_cmd_ok(IMAPSession *session, GPtrArray *argbuf)
2765 while ((ok = imap_gen_recv(session, &buf))
2767 // make sure data is long enough for any substring of buf
2768 data = alloca(strlen(buf) + 1);
2770 if (buf[0] == '*' && buf[1] == ' ') {
2773 g_ptr_array_add(argbuf, g_strdup(buf + 2));
2775 if (sscanf(buf + 2, "%d %s", &num, data) < 2)
2778 if (!strcmp(data, "EXISTS")) {
2779 session->exists = num;
2780 session->folder_content_changed = TRUE;
2783 if(!strcmp(data, "EXPUNGE")) {
2785 session->folder_content_changed = TRUE;
2791 if (sscanf(buf, "%d %s", &cmd_num, data) < 2) {
2794 } else if (cmd_num == session->cmd_count &&
2795 !strcmp(data, "OK")) {
2797 g_ptr_array_add(argbuf, g_strdup(buf));
2799 return IMAP_SUCCESS;
2810 static void imap_gen_send(IMAPSession *session, const gchar *format, ...)
2812 gchar buf[IMAPBUFSIZE];
2813 gchar tmp[IMAPBUFSIZE];
2817 va_start(args, format);
2818 g_vsnprintf(tmp, sizeof(tmp), format, args);
2821 session->cmd_count++;
2823 g_snprintf(buf, sizeof(buf), "%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));
2833 static gint imap_gen_recv(IMAPSession *session, gchar **buf)
2835 if ((*buf = sock_getline(SESSION(session)->sock)) == NULL)
2840 log_print("IMAP4< %s\n", *buf);
2842 return IMAP_SUCCESS;
2846 /* misc utility functions */
2848 static gchar *strchr_cpy(const gchar *src, gchar ch, gchar *dest, gint len)
2853 tmp = strchr(src, ch);
2857 memcpy(dest, src, MIN(tmp - src, len - 1));
2858 dest[MIN(tmp - src, len - 1)] = '\0';
2863 static gchar *get_quoted(const gchar *src, gchar ch, gchar *dest, gint len)
2865 const gchar *p = src;
2868 g_return_val_if_fail(*p == ch, NULL);
2873 while (*p != '\0' && *p != ch) {
2875 if (*p == '\\' && *(p + 1) != '\0')
2884 return (gchar *)(*p == ch ? p + 1 : p);
2887 static gchar *search_array_contain_str(GPtrArray *array, const gchar *str)
2891 for (i = 0; i < array->len; i++) {
2894 tmp = g_ptr_array_index(array, i);
2895 if (strstr(tmp, str) != NULL)
2902 static gchar *search_array_str(GPtrArray *array, const gchar *str)
2909 for (i = 0; i < array->len; i++) {
2912 tmp = g_ptr_array_index(array, i);
2913 if (!strncmp(tmp, str, len))
2920 static void imap_path_separator_subst(gchar *str, gchar separator)
2923 gboolean in_escape = FALSE;
2925 if (!separator || separator == '/') return;
2927 for (p = str; *p != '\0'; p++) {
2928 if (*p == '/' && !in_escape)
2930 else if (*p == '&' && *(p + 1) != '-' && !in_escape)
2932 else if (*p == '-' && in_escape)
2937 static gchar *imap_modified_utf7_to_locale(const gchar *mutf7_str)
2940 return g_strdup(mutf7_str);
2942 static iconv_t cd = (iconv_t)-1;
2943 static gboolean iconv_ok = TRUE;
2946 size_t norm_utf7_len;
2948 gchar *to_str, *to_p;
2950 gboolean in_escape = FALSE;
2952 if (!iconv_ok) return g_strdup(mutf7_str);
2954 if (cd == (iconv_t)-1) {
2955 cd = iconv_open(conv_get_current_charset_str(), "UTF-7");
2956 if (cd == (iconv_t)-1) {
2957 g_warning("iconv cannot convert UTF-7 to %s\n",
2958 conv_get_current_charset_str());
2960 return g_strdup(mutf7_str);
2964 norm_utf7 = g_string_new(NULL);
2966 for (p = mutf7_str; *p != '\0'; p++) {
2967 /* replace: '&' -> '+',
2969 escaped ',' -> '/' */
2970 if (!in_escape && *p == '&') {
2971 if (*(p + 1) != '-') {
2972 g_string_append_c(norm_utf7, '+');
2975 g_string_append_c(norm_utf7, '&');
2978 } else if (in_escape && *p == ',') {
2979 g_string_append_c(norm_utf7, '/');
2980 } else if (in_escape && *p == '-') {
2981 g_string_append_c(norm_utf7, '-');
2984 g_string_append_c(norm_utf7, *p);
2988 norm_utf7_p = norm_utf7->str;
2989 norm_utf7_len = norm_utf7->len;
2990 to_len = strlen(mutf7_str) * 5;
2991 to_p = to_str = g_malloc(to_len + 1);
2993 if (iconv(cd, (ICONV_CONST gchar **)&norm_utf7_p, &norm_utf7_len,
2994 &to_p, &to_len) == -1) {
2995 g_warning(_("iconv cannot convert UTF-7 to %s\n"),
2996 conv_get_current_charset_str());
2997 g_string_free(norm_utf7, TRUE);
2999 return g_strdup(mutf7_str);
3002 /* second iconv() call for flushing */
3003 iconv(cd, NULL, NULL, &to_p, &to_len);
3004 g_string_free(norm_utf7, TRUE);
3008 #endif /* !HAVE_ICONV */
3011 static gchar *imap_locale_to_modified_utf7(const gchar *from)
3014 return g_strdup(from);
3016 static iconv_t cd = (iconv_t)-1;
3017 static gboolean iconv_ok = TRUE;
3018 gchar *norm_utf7, *norm_utf7_p;
3019 size_t from_len, norm_utf7_len;
3021 gchar *from_tmp, *to, *p;
3022 gboolean in_escape = FALSE;
3024 if (!iconv_ok) return g_strdup(from);
3026 if (cd == (iconv_t)-1) {
3027 cd = iconv_open("UTF-7", conv_get_current_charset_str());
3028 if (cd == (iconv_t)-1) {
3029 g_warning("iconv cannot convert %s to UTF-7\n",
3030 conv_get_current_charset_str());
3032 return g_strdup(from);
3036 Xstrdup_a(from_tmp, from, return g_strdup(from));
3037 from_len = strlen(from);
3038 norm_utf7_len = from_len * 5;
3039 Xalloca(norm_utf7, norm_utf7_len + 1, return g_strdup(from));
3040 norm_utf7_p = norm_utf7;
3042 #define IS_PRINT(ch) (isprint(ch) && isascii(ch))
3044 while (from_len > 0) {
3045 if (IS_PRINT(*from_tmp)) {
3046 /* printable ascii char */
3047 *norm_utf7_p = *from_tmp;
3054 /* unprintable char: convert to UTF-7 */
3056 !IS_PRINT(from_tmp[mblen]) && mblen < from_len;
3060 if (iconv(cd, (ICONV_CONST gchar **)&from_tmp, &mblen,
3061 &norm_utf7_p, &norm_utf7_len) == -1) {
3062 g_warning("iconv cannot convert %s to UTF-7\n",
3063 conv_get_current_charset_str());
3064 return g_strdup(from);
3067 /* second iconv() call for flushing */
3068 iconv(cd, NULL, NULL, &norm_utf7_p, &norm_utf7_len);
3074 *norm_utf7_p = '\0';
3075 to_str = g_string_new(NULL);
3076 for (p = norm_utf7; p < norm_utf7_p; p++) {
3077 /* replace: '&' -> "&-",
3079 escaped '/' -> ',' */
3080 if (!in_escape && *p == '&') {
3081 g_string_append(to_str, "&-");
3082 } else if (!in_escape && *p == '+') {
3083 g_string_append_c(to_str, '&');
3085 } else if (in_escape && *p == '/') {
3086 g_string_append_c(to_str, ',');
3087 } else if (in_escape && *p == '-') {
3089 g_string_append_c(to_str, '-');
3091 g_string_append_c(to_str, *p);
3097 g_string_append_c(to_str, '-');
3101 g_string_free(to_str, FALSE);
3104 #endif /* !HAVE_ICONV */
3107 static gboolean imap_rename_folder_func(GNode *node, gpointer data)
3109 FolderItem *item = node->data;
3110 gchar **paths = data;
3111 const gchar *oldpath = paths[0];
3112 const gchar *newpath = paths[1];
3114 gchar *new_itempath;
3117 oldpathlen = strlen(oldpath);
3118 if (strncmp(oldpath, item->path, oldpathlen) != 0) {
3119 g_warning("path doesn't match: %s, %s\n", oldpath, item->path);
3123 base = item->path + oldpathlen;
3124 while (*base == G_DIR_SEPARATOR) base++;
3126 new_itempath = g_strdup(newpath);
3128 new_itempath = g_strconcat(newpath, G_DIR_SEPARATOR_S, base,
3131 item->path = new_itempath;
3136 static gint get_list_of_uids(Folder *folder, IMAPFolderItem *item, GSList **msgnum_list)
3138 gint ok, nummsgs = 0, lastuid_old;
3139 IMAPSession *session;
3140 GSList *uidlist, *elem;
3143 session = imap_session_get(folder);
3144 g_return_val_if_fail(session != NULL, -1);
3146 ok = imap_select(session, IMAP_FOLDER(folder), item->item.path,
3147 NULL, NULL, NULL, NULL);
3148 if (ok != IMAP_SUCCESS)
3151 cmd_buf = g_strdup_printf("UID %d:*", item->lastuid + 1);
3152 ok = imap_cmd_search(session, cmd_buf, &uidlist);
3155 if (ok == IMAP_SOCKET) {
3156 session_destroy((Session *)session);
3157 ((RemoteFolder *)folder)->session = NULL;
3161 if (ok != IMAP_SUCCESS) {
3165 argbuf = g_ptr_array_new();
3167 cmd_buf = g_strdup_printf("UID FETCH %d:* (UID)", item->lastuid + 1);
3168 imap_gen_send(session, cmd_buf);
3170 ok = imap_cmd_ok(session, argbuf);
3171 if (ok != IMAP_SUCCESS) {
3172 ptr_array_free_strings(argbuf);
3173 g_ptr_array_free(argbuf, TRUE);
3177 for(i = 0; i < argbuf->len; i++) {
3180 if((ret = sscanf(g_ptr_array_index(argbuf, i),
3181 "%*d FETCH (UID %d)", &msgnum)) == 1)
3182 uidlist = g_slist_prepend(uidlist, GINT_TO_POINTER(msgnum));
3184 ptr_array_free_strings(argbuf);
3185 g_ptr_array_free(argbuf, TRUE);
3188 lastuid_old = item->lastuid;
3189 *msgnum_list = g_slist_copy(item->uid_list);
3190 nummsgs = g_slist_length(*msgnum_list);
3191 debug_print("Got %d uids from cache\n", g_slist_length(item->uid_list));
3193 for (elem = uidlist; elem != NULL; elem = g_slist_next(elem)) {
3196 msgnum = GPOINTER_TO_INT(elem->data);
3197 if (msgnum > lastuid_old) {
3198 *msgnum_list = g_slist_prepend(*msgnum_list, GINT_TO_POINTER(msgnum));
3199 item->uid_list = g_slist_prepend(item->uid_list, GINT_TO_POINTER(msgnum));
3202 if(msgnum > item->lastuid)
3203 item->lastuid = msgnum;
3206 g_slist_free(uidlist);
3211 gint imap_get_num_list(Folder *folder, FolderItem *_item, GSList **msgnum_list)
3213 IMAPFolderItem *item = (IMAPFolderItem *)_item;
3214 IMAPSession *session;
3215 gint ok, nummsgs = 0, exists, recent, unseen, uid_val, uid_next;
3218 gboolean selected_folder;
3220 g_return_val_if_fail(folder != NULL, -1);
3221 g_return_val_if_fail(item != NULL, -1);
3222 g_return_val_if_fail(item->item.path != NULL, -1);
3223 g_return_val_if_fail(FOLDER_CLASS(folder) == &imap_class, -1);
3224 g_return_val_if_fail(folder->account != NULL, -1);
3226 session = imap_session_get(folder);
3227 g_return_val_if_fail(session != NULL, -1);
3229 selected_folder = (session->mbox != NULL) &&
3230 (!strcmp(session->mbox, item->item.path));
3231 if (selected_folder) {
3232 ok = imap_cmd_noop(session);
3233 if (ok != IMAP_SUCCESS)
3235 exists = session->exists;
3237 ok = imap_status(session, IMAP_FOLDER(folder), item->item.path,
3238 &exists, &recent, &uid_next, &uid_val, &unseen);
3239 if (ok != IMAP_SUCCESS)
3243 /* If old uid_next matches new uid_next we can be sure no message
3244 was added to the folder */
3245 if (( selected_folder && !session->folder_content_changed) ||
3246 (!selected_folder && uid_next == item->uid_next)) {
3247 nummsgs = g_slist_length(item->uid_list);
3249 /* If number of messages is still the same we
3250 know our caches message numbers are still valid,
3251 otherwise if the number of messages has decrease
3252 we discard our cache to start a new scan to find
3253 out which numbers have been removed */
3254 if (exists == nummsgs) {
3255 *msgnum_list = g_slist_copy(item->uid_list);
3257 } else if (exists < nummsgs) {
3258 debug_print("Freeing imap uid cache");
3260 g_slist_free(item->uid_list);
3261 item->uid_list = NULL;
3264 if (!selected_folder)
3265 item->uid_next = uid_next;
3268 *msgnum_list = NULL;
3272 nummsgs = get_list_of_uids(folder, item, &uidlist);
3274 if (nummsgs != exists) {
3275 /* Cache contains more messages then folder, we have cached
3276 an old UID of a message that was removed and new messages
3277 have been added too, otherwise the uid_next check would
3279 debug_print("Freeing imap uid cache");
3281 g_slist_free(item->uid_list);
3282 item->uid_list = NULL;
3284 g_slist_free(*msgnum_list);
3286 nummsgs = get_list_of_uids(folder, item, &uidlist);
3289 *msgnum_list = uidlist;
3291 dir = folder_item_get_path((FolderItem *)item);
3292 debug_print("removing old messages from %s\n", dir);
3293 remove_numbered_files_not_in_list(dir, *msgnum_list);
3299 static MsgInfo *imap_parse_msg(const gchar *file, FolderItem *item)
3304 flags.perm_flags = MSG_NEW|MSG_UNREAD;
3305 flags.tmp_flags = 0;
3307 g_return_val_if_fail(item != NULL, NULL);
3308 g_return_val_if_fail(file != NULL, NULL);
3310 if (item->stype == F_QUEUE) {
3311 MSG_SET_TMP_FLAGS(flags, MSG_QUEUED);
3312 } else if (item->stype == F_DRAFT) {
3313 MSG_SET_TMP_FLAGS(flags, MSG_DRAFT);
3316 msginfo = procheader_parse_file(file, flags, FALSE, FALSE);
3317 if (!msginfo) return NULL;
3319 msginfo->folder = item;
3324 GSList *imap_get_msginfos(Folder *folder, FolderItem *item, GSList *msgnum_list)
3326 IMAPSession *session;
3327 MsgInfoList *ret = NULL;
3330 g_return_val_if_fail(folder != NULL, NULL);
3331 g_return_val_if_fail(item != NULL, NULL);
3332 g_return_val_if_fail(msgnum_list != NULL, NULL);
3334 session = imap_session_get(folder);
3335 g_return_val_if_fail(session != NULL, NULL);
3337 ok = imap_select(session, IMAP_FOLDER(folder), item->path,
3338 NULL, NULL, NULL, NULL);
3339 if (ok != IMAP_SUCCESS)
3342 if (!(item->stype == F_QUEUE || item->stype == F_DRAFT)) {
3343 ret = g_slist_concat(ret,
3344 imap_get_uncached_messages(
3345 session, item, msgnum_list));
3347 MsgNumberList *sorted_list, *elem;
3348 gint startnum, lastnum;
3350 sorted_list = g_slist_sort(g_slist_copy(msgnum_list), g_int_compare);
3352 startnum = lastnum = GPOINTER_TO_INT(sorted_list->data);
3354 for (elem = sorted_list;; elem = g_slist_next(elem)) {
3358 num = GPOINTER_TO_INT(elem->data);
3360 if (num > lastnum + 1 || elem == NULL) {
3362 for (i = startnum; i <= lastnum; ++i) {
3365 file = imap_fetch_msg(folder, item, i);
3367 MsgInfo *msginfo = imap_parse_msg(file, item);
3368 if (msginfo != NULL) {
3369 msginfo->msgnum = i;
3370 ret = g_slist_append(ret, msginfo);
3384 g_slist_free(sorted_list);
3390 MsgInfo *imap_get_msginfo(Folder *folder, FolderItem *item, gint uid)
3392 MsgInfo *msginfo = NULL;
3393 MsgInfoList *msginfolist;
3394 MsgNumberList numlist;
3396 numlist.next = NULL;
3397 numlist.data = GINT_TO_POINTER(uid);
3399 msginfolist = imap_get_msginfos(folder, item, &numlist);
3400 if (msginfolist != NULL) {
3401 msginfo = msginfolist->data;
3402 g_slist_free(msginfolist);
3408 gboolean imap_check_msgnum_validity(Folder *folder, FolderItem *_item)
3410 IMAPSession *session;
3411 IMAPFolderItem *item = (IMAPFolderItem *)_item;
3412 gint ok, exists = 0, recent = 0, unseen = 0;
3413 guint32 uid_next, uid_validity = 0;
3415 g_return_val_if_fail(folder != NULL, FALSE);
3416 g_return_val_if_fail(item != NULL, FALSE);
3417 g_return_val_if_fail(item->item.folder != NULL, FALSE);
3418 g_return_val_if_fail(FOLDER_CLASS(item->item.folder) == &imap_class, FALSE);
3420 session = imap_session_get(folder);
3421 g_return_val_if_fail(session != NULL, FALSE);
3423 ok = imap_status(session, IMAP_FOLDER(folder), item->item.path,
3424 &exists, &recent, &uid_next, &uid_validity, &unseen);
3425 if (ok != IMAP_SUCCESS)
3428 if(item->item.mtime == uid_validity)
3431 debug_print("Freeing imap uid cache");
3433 g_slist_free(item->uid_list);
3434 item->uid_list = NULL;
3436 item->item.mtime = uid_validity;
3438 imap_delete_all_cached_messages((FolderItem *)item);
3443 void imap_change_flags(Folder *folder, FolderItem *item, MsgInfo *msginfo, MsgPermFlags newflags)
3445 IMAPSession *session;
3446 IMAPFlags flags_set = 0, flags_unset = 0;
3447 gint ok = IMAP_SUCCESS;
3448 MsgNumberList numlist;
3450 g_return_if_fail(folder != NULL);
3451 g_return_if_fail(folder->klass == &imap_class);
3452 g_return_if_fail(item != NULL);
3453 g_return_if_fail(item->folder == folder);
3454 g_return_if_fail(msginfo != NULL);
3455 g_return_if_fail(msginfo->folder == item);
3457 session = imap_session_get(folder);
3458 if (!session) return;
3460 if ((ok = imap_select(session, IMAP_FOLDER(folder), msginfo->folder->path,
3461 NULL, NULL, NULL, NULL)) != IMAP_SUCCESS)
3464 if (!MSG_IS_MARKED(msginfo->flags) && (newflags & MSG_MARKED))
3465 flags_set |= IMAP_FLAG_FLAGGED;
3466 if ( MSG_IS_MARKED(msginfo->flags) && !(newflags & MSG_MARKED))
3467 flags_unset |= IMAP_FLAG_FLAGGED;
3469 if (!MSG_IS_UNREAD(msginfo->flags) && (newflags & MSG_UNREAD))
3470 flags_unset |= IMAP_FLAG_SEEN;
3471 if ( MSG_IS_UNREAD(msginfo->flags) && !(newflags & MSG_UNREAD))
3472 flags_set |= IMAP_FLAG_SEEN;
3474 if (!MSG_IS_REPLIED(msginfo->flags) && (newflags & MSG_REPLIED))
3475 flags_set |= IMAP_FLAG_ANSWERED;
3476 if ( MSG_IS_REPLIED(msginfo->flags) && !(newflags & MSG_REPLIED))
3477 flags_set |= IMAP_FLAG_ANSWERED;
3479 numlist.next = NULL;
3480 numlist.data = GINT_TO_POINTER(msginfo->msgnum);
3483 ok = imap_set_message_flags(session, &numlist, flags_set, TRUE);
3484 if (ok != IMAP_SUCCESS) return;
3488 ok = imap_set_message_flags(session, &numlist, flags_unset, FALSE);
3489 if (ok != IMAP_SUCCESS) return;
3492 msginfo->flags.perm_flags = newflags;