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"
55 #include "inputdialog.h"
58 typedef struct _IMAPFolder IMAPFolder;
59 typedef struct _IMAPSession IMAPSession;
60 typedef struct _IMAPNameSpace IMAPNameSpace;
61 typedef struct _IMAPFolderItem IMAPFolderItem;
63 #include "prefs_account.h"
65 #define IMAP_FOLDER(obj) ((IMAPFolder *)obj)
66 #define IMAP_SESSION(obj) ((IMAPSession *)obj)
72 /* list of IMAPNameSpace */
84 time_t last_access_time;
85 gboolean authenticated;
87 gboolean folder_content_changed;
97 #define IMAP_SUCCESS 0
99 #define IMAP_AUTHFAIL 3
100 #define IMAP_PROTOCOL 4
101 #define IMAP_SYNTAX 5
105 #define IMAPBUFSIZE 8192
106 #define IMAPCMDLIMIT 1000
110 IMAP_FLAG_SEEN = 1 << 0,
111 IMAP_FLAG_ANSWERED = 1 << 1,
112 IMAP_FLAG_FLAGGED = 1 << 2,
113 IMAP_FLAG_DELETED = 1 << 3,
114 IMAP_FLAG_DRAFT = 1 << 4
117 #define IMAP_IS_SEEN(flags) ((flags & IMAP_FLAG_SEEN) != 0)
118 #define IMAP_IS_ANSWERED(flags) ((flags & IMAP_FLAG_ANSWERED) != 0)
119 #define IMAP_IS_FLAGGED(flags) ((flags & IMAP_FLAG_FLAGGED) != 0)
120 #define IMAP_IS_DELETED(flags) ((flags & IMAP_FLAG_DELETED) != 0)
121 #define IMAP_IS_DRAFT(flags) ((flags & IMAP_FLAG_DRAFT) != 0)
124 #define IMAP4_PORT 143
126 #define IMAPS_PORT 993
129 #define QUOTE_IF_REQUIRED(out, str) \
131 if (*str != '"' && strpbrk(str, " \t(){}%*") != NULL) { \
135 len = strlen(str) + 3; \
136 Xalloca(__tmp, len, return IMAP_ERROR); \
137 g_snprintf(__tmp, len, "\"%s\"", str); \
140 Xstrdup_a(out, str, return IMAP_ERROR); \
144 typedef gchar * IMAPSet;
146 struct _IMAPFolderItem
155 static Folder *imap_folder_new(const gchar * name, const gchar * path);
156 static void imap_folder_destroy(Folder * folder);
158 static IMAPSession *imap_session_new(const PrefsAccount * account);
159 static void imap_session_authenticate(IMAPSession * session,
160 const PrefsAccount * account);
161 static void imap_session_destroy(Session * session);
163 static gchar *imap_fetch_msg(Folder * folder, FolderItem * item, gint uid);
164 static gint imap_add_msg(Folder * folder,
166 const gchar * file, gboolean remove_source);
168 static gint imap_copy_msg(Folder * folder,
169 FolderItem * dest, MsgInfo * msginfo);
171 static gint imap_remove_msg(Folder * folder, FolderItem * item, gint uid);
172 static gint imap_remove_all_msg(Folder * folder, FolderItem * item);
174 static gboolean imap_is_msg_changed(Folder * folder,
175 FolderItem * item, MsgInfo * msginfo);
177 static void imap_scan_tree(Folder * folder);
179 static gint imap_create_tree(Folder * folder);
181 static FolderItem *imap_create_folder(Folder * folder,
184 static gint imap_rename_folder(Folder * folder,
185 FolderItem * item, const gchar * name);
186 static gint imap_remove_folder(Folder * folder, FolderItem * item);
189 static void imap_folder_init (Folder *folder,
193 static FolderItem *imap_folder_item_new (Folder *folder);
194 static void imap_folder_item_destroy (Folder *folder,
197 static IMAPSession *imap_session_get (Folder *folder);
199 static gint imap_auth (IMAPSession *session,
204 static gint imap_scan_tree_recursive (IMAPSession *session,
206 static GSList *imap_parse_list (IMAPFolder *folder,
207 IMAPSession *session,
208 const gchar *real_path,
211 static void imap_create_missing_folders (Folder *folder);
212 static FolderItem *imap_create_special_folder
214 SpecialFolderItemType stype,
217 static gint imap_do_copy (Folder *folder,
220 gboolean remove_source);
222 static void imap_delete_all_cached_messages (FolderItem *item);
225 static SockInfo *imap_open (const gchar *server,
229 static SockInfo *imap_open (const gchar *server,
234 static SockInfo *imap_open_tunnel(const gchar *server,
235 const gchar *tunnelcmd,
238 static SockInfo *imap_open_tunnel(const gchar *server,
239 const gchar *tunnelcmd);
243 static SockInfo *imap_init_sock(SockInfo *sock, SSLType ssl_type);
245 static SockInfo *imap_init_sock(SockInfo *sock);
248 static gint imap_set_message_flags (IMAPSession *session,
249 MsgNumberList *numlist,
252 static gint imap_select (IMAPSession *session,
258 guint32 *uid_validity);
259 static gint imap_status (IMAPSession *session,
265 guint32 *uid_validity,
268 static void imap_parse_namespace (IMAPSession *session,
270 static void imap_get_namespace_by_list (IMAPSession *session,
272 static IMAPNameSpace *imap_find_namespace (IMAPFolder *folder,
274 static gchar imap_get_path_separator (IMAPFolder *folder,
276 static gchar *imap_get_real_path (IMAPFolder *folder,
279 static gchar *imap_parse_atom (SockInfo *sock,
284 static MsgFlags imap_parse_flags (const gchar *flag_str);
285 static MsgInfo *imap_parse_envelope (SockInfo *sock,
288 static gint imap_greeting (IMAPSession *session);
289 static gboolean imap_has_capability (IMAPSession *session,
291 void imap_free_capabilities (IMAPSession *session);
292 static const IMAPSet numberlist_to_imapset
293 (MsgNumberList *list);
295 /* low-level IMAP4rev1 commands */
296 static gint imap_cmd_authenticate
297 (IMAPSession *session,
301 static gint imap_cmd_login (IMAPSession *sock,
304 static gint imap_cmd_logout (IMAPSession *sock);
305 static gint imap_cmd_noop (IMAPSession *sock);
306 static gint imap_cmd_starttls (IMAPSession *sock);
307 static gint imap_cmd_namespace (IMAPSession *sock,
309 static gint imap_cmd_list (IMAPSession *session,
311 const gchar *mailbox,
313 static gint imap_cmd_do_select (IMAPSession *sock,
319 guint32 *uid_validity);
320 static gint imap_cmd_select (IMAPSession *sock,
325 guint32 *uid_validity);
326 static gint imap_cmd_examine (IMAPSession *sock,
331 guint32 *uid_validity);
332 static gint imap_cmd_create (IMAPSession *sock,
333 const gchar *folder);
334 static gint imap_cmd_rename (IMAPSession *sock,
335 const gchar *oldfolder,
336 const gchar *newfolder);
337 static gint imap_cmd_delete (IMAPSession *sock,
338 const gchar *folder);
339 static gint imap_cmd_envelope (IMAPSession *sock,
341 static gint imap_cmd_fetch (IMAPSession *sock,
343 const gchar *filename);
344 static gint imap_cmd_append (IMAPSession *session,
345 const gchar *destfolder,
348 static gint imap_cmd_copy (IMAPSession *session,
350 const gchar *destfolder,
352 static gint imap_cmd_store (IMAPSession *sock,
355 static gint imap_cmd_expunge (IMAPSession *sock);
357 static gint imap_cmd_ok (IMAPSession *session,
359 static void imap_gen_send (IMAPSession *sock,
360 const gchar *format, ...);
361 static gint imap_gen_recv (IMAPSession *sock,
364 /* misc utility functions */
365 static gchar *strchr_cpy (const gchar *src,
369 static gchar *get_quoted (const gchar *src,
373 static gchar *search_array_contain_str (GPtrArray *array,
375 static gchar *search_array_str (GPtrArray *array,
377 static void imap_path_separator_subst (gchar *str,
380 static gchar *imap_modified_utf7_to_locale (const gchar *mutf7_str);
381 static gchar *imap_locale_to_modified_utf7 (const gchar *from);
383 static gboolean imap_rename_folder_func (GNode *node,
385 static gint imap_get_num_list (Folder *folder,
388 static GSList *imap_get_msginfos (Folder *folder,
390 GSList *msgnum_list);
391 static MsgInfo *imap_get_msginfo (Folder *folder,
394 static gboolean imap_check_msgnum_validity (Folder *folder,
396 static void imap_change_flags (Folder *folder,
399 MsgPermFlags newflags);
400 static gchar *imap_folder_get_path (Folder *folder);
401 static gchar *imap_item_get_path (Folder *folder,
404 FolderClass imap_class =
410 /* Folder functions */
416 /* FolderItem functions */
417 imap_folder_item_new,
418 imap_folder_item_destroy,
427 imap_check_msgnum_validity,
429 /* Message functions */
441 FolderClass *imap_get_class(void)
446 Folder *imap_folder_new(const gchar *name, const gchar *path)
450 folder = (Folder *)g_new0(IMAPFolder, 1);
451 folder->klass = &imap_class;
452 imap_folder_init(folder, name, path);
457 void imap_folder_destroy(Folder *folder)
461 dir = imap_folder_get_path(folder);
462 if (is_dir_exist(dir))
463 remove_dir_recursive(dir);
466 folder_remote_folder_destroy(REMOTE_FOLDER(folder));
469 static void imap_folder_init(Folder *folder, const gchar *name,
472 folder_remote_folder_init((Folder *)folder, name, path);
475 static FolderItem *imap_folder_item_new(Folder *folder)
477 IMAPFolderItem *item;
479 item = g_new0(IMAPFolderItem, 1);
482 item->uid_list = NULL;
484 return (FolderItem *)item;
487 static void imap_folder_item_destroy(Folder *folder, FolderItem *_item)
489 IMAPFolderItem *item = (IMAPFolderItem *)_item;
491 g_return_if_fail(item != NULL);
492 g_slist_free(item->uid_list);
497 static gboolean imap_reset_uid_lists_func(GNode *node, gpointer data)
499 IMAPFolderItem *item = (IMAPFolderItem *)node->data;
503 g_slist_free(item->uid_list);
504 item->uid_list = NULL;
509 static void imap_reset_uid_lists(Folder *folder)
511 if(folder->node == NULL)
514 /* Destroy all uid lists and rest last uid */
515 g_node_traverse(folder->node, G_IN_ORDER, G_TRAVERSE_ALL, -1, imap_reset_uid_lists_func, NULL);
518 static gint imap_auth(IMAPSession *session, const gchar *user, const gchar *pass,
521 if (type == 0 || type == IMAP_AUTH_LOGIN)
522 return imap_cmd_login(session, user, pass);
524 return imap_cmd_authenticate(session, user, pass, type);
527 static IMAPSession *imap_session_get(Folder *folder)
529 RemoteFolder *rfolder = REMOTE_FOLDER(folder);
530 IMAPSession *session = NULL;
532 g_return_val_if_fail(folder != NULL, NULL);
533 g_return_val_if_fail(FOLDER_CLASS(folder) == &imap_class, NULL);
534 g_return_val_if_fail(folder->account != NULL, NULL);
536 /* Make sure we have a session */
537 if (rfolder->session != NULL) {
538 session = IMAP_SESSION(rfolder->session);
540 imap_reset_uid_lists(folder);
541 session = imap_session_new(folder->account);
546 if (SESSION(session)->sock->state == CONN_DISCONNECTED) {
547 debug_print("IMAP server disconnected\n");
548 session_destroy(SESSION(session));
549 imap_reset_uid_lists(folder);
550 session = imap_session_new(folder->account);
553 /* Make sure session is authenticated */
554 if (!IMAP_SESSION(session)->authenticated)
555 imap_session_authenticate(IMAP_SESSION(session), folder->account);
556 if (!IMAP_SESSION(session)->authenticated) {
557 session_destroy(SESSION(session));
558 rfolder->session = NULL;
562 /* Make sure we have parsed the IMAP namespace */
563 imap_parse_namespace(IMAP_SESSION(session),
564 IMAP_FOLDER(folder));
566 /* I think the point of this code is to avoid sending a
567 * keepalive if we've used the session recently and therefore
568 * think it's still alive. Unfortunately, most of the code
569 * does not yet check for errors on the socket, and so if the
570 * connection drops we don't notice until the timeout expires.
571 * A better solution than sending a NOOP every time would be
572 * for every command to be prepared to retry until it is
573 * successfully sent. -- mbp */
574 if (time(NULL) - session->last_access_time > SESSION_TIMEOUT) {
575 /* verify that the session is still alive */
576 if (imap_cmd_noop(session) != IMAP_SUCCESS) {
577 /* Check if this is the first try to establish a
578 connection, if yes we don't try to reconnect */
579 if (rfolder->session == NULL) {
580 log_warning(_("Connecting %s failed"),
581 folder->account->recv_server);
582 session_destroy(SESSION(session));
585 log_warning(_("IMAP4 connection to %s has been"
586 " disconnected. Reconnecting...\n"),
587 folder->account->recv_server);
588 session_destroy(SESSION(session));
589 /* Clear folders session to make imap_session_get create
590 a new session, because of rfolder->session == NULL
591 it will not try to reconnect again and so avoid an
593 rfolder->session = NULL;
594 session = imap_session_get(folder);
599 rfolder->session = SESSION(session);
601 session->last_access_time = time(NULL);
603 return IMAP_SESSION(session);
606 IMAPSession *imap_session_new(const PrefsAccount *account)
608 IMAPSession *session;
613 /* FIXME: IMAP over SSL only... */
616 port = account->set_imapport ? account->imapport
617 : account->ssl_imap == SSL_TUNNEL ? IMAPS_PORT : IMAP4_PORT;
618 ssl_type = account->ssl_imap;
620 port = account->set_imapport ? account->imapport
624 if (account->set_tunnelcmd) {
625 log_message(_("creating tunneled IMAP4 connection\n"));
627 if ((imap_sock = imap_open_tunnel(account->recv_server,
631 if ((imap_sock = imap_open_tunnel(account->recv_server,
632 account->tunnelcmd)) == NULL)
636 g_return_val_if_fail(account->recv_server != NULL, NULL);
638 log_message(_("creating IMAP4 connection to %s:%d ...\n"),
639 account->recv_server, port);
642 if ((imap_sock = imap_open(account->recv_server, port,
645 if ((imap_sock = imap_open(account->recv_server, port)) == NULL)
650 session = g_new0(IMAPSession, 1);
651 session_init(SESSION(session));
652 SESSION(session)->type = SESSION_IMAP;
653 SESSION(session)->server = g_strdup(account->recv_server);
654 SESSION(session)->sock = imap_sock;
656 SESSION(session)->destroy = imap_session_destroy;
658 session->capability = NULL;
660 session->mbox = NULL;
661 session->authenticated = FALSE;
662 session->cmd_count = 0;
664 /* Only need to log in if the connection was not PREAUTH */
665 if (imap_greeting(session) != IMAP_SUCCESS) {
666 session_destroy(SESSION(session));
671 if (account->ssl_imap == SSL_STARTTLS && imap_has_capability(session, "STARTTLS")) {
674 ok = imap_cmd_starttls(session);
675 if (ok != IMAP_SUCCESS) {
676 log_warning(_("Can't start TLS session.\n"));
677 session_destroy(SESSION(session));
680 if (!ssl_init_socket_with_method(SESSION(session)->sock, SSL_METHOD_TLSv1)) {
681 session_destroy(SESSION(session));
685 imap_free_capabilities(session);
686 session->authenticated = FALSE;
687 session->cmd_count = 1;
689 if (imap_greeting(session) != IMAP_SUCCESS) {
690 session_destroy(SESSION(session));
695 log_message("IMAP connection is %s-authenticated\n",
696 (session->authenticated) ? "pre" : "un");
701 void imap_session_authenticate(IMAPSession *session, const PrefsAccount *account)
705 g_return_if_fail(account->userid != NULL);
707 pass = account->passwd;
710 tmp_pass = input_dialog_query_password(account->recv_server, account->userid);
713 Xstrdup_a(pass, tmp_pass, {g_free(tmp_pass); return;});
717 if (imap_auth(session, account->userid, pass, account->imap_auth_type) != IMAP_SUCCESS) {
718 imap_cmd_logout(session);
722 session->authenticated = TRUE;
725 void imap_session_destroy(Session *session)
727 sock_close(session->sock);
728 session->sock = NULL;
730 g_free(IMAP_SESSION(session)->mbox);
731 imap_free_capabilities(IMAP_SESSION(session));
734 gchar *imap_fetch_msg(Folder *folder, FolderItem *item, gint uid)
736 gchar *path, *filename;
737 IMAPSession *session;
740 g_return_val_if_fail(folder != NULL, NULL);
741 g_return_val_if_fail(item != NULL, NULL);
743 path = folder_item_get_path(item);
744 if (!is_dir_exist(path))
746 filename = g_strconcat(path, G_DIR_SEPARATOR_S, itos(uid), NULL);
749 if (is_file_exist(filename)) {
750 debug_print("message %d has been already cached.\n", uid);
754 session = imap_session_get(folder);
760 ok = imap_select(session, IMAP_FOLDER(folder), item->path,
761 NULL, NULL, NULL, NULL);
762 if (ok != IMAP_SUCCESS) {
763 g_warning("can't select mailbox %s\n", item->path);
768 debug_print("getting message %d...\n", uid);
769 ok = imap_cmd_fetch(session, (guint32)uid, filename);
771 if (ok != IMAP_SUCCESS) {
772 g_warning("can't fetch message %d\n", uid);
780 gint imap_add_msg(Folder *folder, FolderItem *dest, const gchar *file,
781 gboolean remove_source)
784 IMAPSession *session;
787 g_return_val_if_fail(folder != NULL, -1);
788 g_return_val_if_fail(dest != NULL, -1);
789 g_return_val_if_fail(file != NULL, -1);
791 session = imap_session_get(folder);
792 if (!session) return -1;
794 destdir = imap_get_real_path(IMAP_FOLDER(folder), dest->path);
795 ok = imap_cmd_append(session, destdir, file, &newuid);
798 if (ok != IMAP_SUCCESS) {
799 g_warning("can't append message %s\n", file);
804 if (unlink(file) < 0)
805 FILE_OP_ERROR(file, "unlink");
811 static gint imap_do_copy(Folder *folder, FolderItem *dest, MsgInfo *msginfo,
812 gboolean remove_source)
815 IMAPSession *session;
819 g_return_val_if_fail(folder != NULL, -1);
820 g_return_val_if_fail(FOLDER_CLASS(folder) == &imap_class, -1);
821 g_return_val_if_fail(dest != NULL, -1);
822 g_return_val_if_fail(msginfo != NULL, -1);
824 session = imap_session_get(folder);
825 if (!session) return -1;
827 if (msginfo->folder == dest) {
828 g_warning("the src folder is identical to the dest.\n");
832 destdir = imap_get_real_path(IMAP_FOLDER(folder), dest->path);
834 /* ensure source folder selected */
835 ok = imap_select(session, IMAP_FOLDER(folder), msginfo->folder->path,
836 NULL, NULL, NULL, NULL);
837 if (ok != IMAP_SUCCESS)
841 debug_print("Moving message %s%c%d to %s ...\n",
842 msginfo->folder->path, G_DIR_SEPARATOR,
843 msginfo->msgnum, destdir);
845 debug_print("Copying message %s%c%d to %s ...\n",
846 msginfo->folder->path, G_DIR_SEPARATOR,
847 msginfo->msgnum, destdir);
849 ok = imap_cmd_copy(session, msginfo->msgnum, destdir, &newuid);
851 if (ok == IMAP_SUCCESS && remove_source) {
852 MsgNumberList numlist;
855 numlist.data = GINT_TO_POINTER(msginfo->msgnum);
857 imap_set_message_flags(session, &numlist,
858 IMAP_FLAG_DELETED, TRUE);
859 ok = imap_cmd_expunge(session);
864 if (ok == IMAP_SUCCESS)
870 gint imap_copy_msg(Folder *folder, FolderItem *dest, MsgInfo *msginfo)
875 g_return_val_if_fail(folder != NULL, -1);
876 g_return_val_if_fail(dest != NULL, -1);
877 g_return_val_if_fail(msginfo != NULL, -1);
878 g_return_val_if_fail(msginfo->folder != NULL, -1);
880 if (folder == msginfo->folder->folder)
881 return imap_do_copy(folder, dest, msginfo, FALSE);
883 srcfile = procmsg_get_message_file(msginfo);
884 if (!srcfile) return -1;
886 ret = imap_add_msg(folder, dest, srcfile, FALSE);
893 gint imap_remove_msg(Folder *folder, FolderItem *item, gint uid)
896 IMAPSession *session;
898 MsgNumberList numlist;
900 g_return_val_if_fail(folder != NULL, -1);
901 g_return_val_if_fail(FOLDER_CLASS(folder) == &imap_class, -1);
902 g_return_val_if_fail(item != NULL, -1);
904 session = imap_session_get(folder);
905 if (!session) return -1;
907 ok = imap_select(session, IMAP_FOLDER(folder), item->path,
908 NULL, NULL, NULL, NULL);
909 if (ok != IMAP_SUCCESS)
913 numlist.data = GINT_TO_POINTER(uid);
915 ok = imap_set_message_flags
916 (IMAP_SESSION(REMOTE_FOLDER(folder)->session),
917 &numlist, IMAP_FLAG_DELETED, TRUE);
918 if (ok != IMAP_SUCCESS) {
919 log_warning(_("can't set deleted flags: %d\n"), uid);
923 ok = imap_cmd_expunge(session);
924 if (ok != IMAP_SUCCESS) {
925 log_warning(_("can't expunge\n"));
929 dir = folder_item_get_path(item);
930 if (is_dir_exist(dir))
931 remove_numbered_files(dir, uid, uid);
937 gint imap_remove_all_msg(Folder *folder, FolderItem *item)
939 gint exists, recent, unseen;
940 guint32 uid_validity;
942 IMAPSession *session;
945 g_return_val_if_fail(folder != NULL, -1);
946 g_return_val_if_fail(item != NULL, -1);
948 session = imap_session_get(folder);
949 if (!session) return -1;
951 ok = imap_select(session, IMAP_FOLDER(folder), item->path,
952 &exists, &recent, &unseen, &uid_validity);
953 if (ok != IMAP_SUCCESS)
958 imap_gen_send(session,
959 "STORE 1:%d +FLAGS (\\Deleted)", exists);
960 ok = imap_cmd_ok(session, NULL);
961 if (ok != IMAP_SUCCESS) {
962 log_warning(_("can't set deleted flags: 1:%d\n"), exists);
966 ok = imap_cmd_expunge(session);
967 if (ok != IMAP_SUCCESS) {
968 log_warning(_("can't expunge\n"));
972 dir = folder_item_get_path(item);
973 if (is_dir_exist(dir))
974 remove_all_numbered_files(dir);
980 gboolean imap_is_msg_changed(Folder *folder, FolderItem *item, MsgInfo *msginfo)
982 /* TODO: properly implement this method */
986 void imap_scan_tree(Folder *folder)
989 IMAPSession *session;
990 gchar *root_folder = NULL;
992 g_return_if_fail(folder != NULL);
993 g_return_if_fail(folder->account != NULL);
995 session = imap_session_get(folder);
998 folder_tree_destroy(folder);
999 item = folder_item_new(folder, folder->name, NULL);
1000 item->folder = folder;
1001 folder->node = g_node_new(item);
1006 if (folder->account->imap_dir && *folder->account->imap_dir) {
1007 Xstrdup_a(root_folder, folder->account->imap_dir, return);
1008 strtailchomp(root_folder, '/');
1009 debug_print("IMAP root directory: %s\n", root_folder);
1012 item = folder_item_new(folder, folder->name, root_folder);
1013 item->folder = folder;
1014 item->no_select = TRUE;
1015 folder->node = g_node_new(item);
1017 imap_scan_tree_recursive(session, item);
1019 imap_create_missing_folders(folder);
1022 static gint imap_scan_tree_recursive(IMAPSession *session, FolderItem *item)
1025 IMAPFolder *imapfolder;
1026 FolderItem *new_item;
1027 GSList *item_list, *cur;
1029 gchar *wildcard_path;
1033 g_return_val_if_fail(item != NULL, -1);
1034 g_return_val_if_fail(item->folder != NULL, -1);
1035 g_return_val_if_fail(item->no_sub == FALSE, -1);
1037 folder = FOLDER(item->folder);
1038 imapfolder = IMAP_FOLDER(folder);
1040 separator = imap_get_path_separator(imapfolder, item->path);
1042 if (item->folder->ui_func)
1043 item->folder->ui_func(folder, item, folder->ui_func_data);
1046 wildcard[0] = separator;
1049 real_path = imap_get_real_path(imapfolder, item->path);
1053 real_path = g_strdup("");
1056 Xstrcat_a(wildcard_path, real_path, wildcard,
1057 {g_free(real_path); return IMAP_ERROR;});
1058 QUOTE_IF_REQUIRED(wildcard_path, wildcard_path);
1060 imap_gen_send(session, "LIST \"\" %s",
1063 strtailchomp(real_path, separator);
1064 item_list = imap_parse_list(imapfolder, session, real_path, NULL);
1067 for (cur = item_list; cur != NULL; cur = cur->next) {
1068 new_item = cur->data;
1069 if (!strcmp(new_item->path, "INBOX")) {
1070 if (!folder->inbox) {
1071 new_item->stype = F_INBOX;
1072 item->folder->inbox = new_item;
1074 folder_item_destroy(new_item);
1077 } else if (!item->parent || item->stype == F_INBOX) {
1080 base = g_basename(new_item->path);
1082 if (!folder->outbox && !strcasecmp(base, "Sent")) {
1083 new_item->stype = F_OUTBOX;
1084 folder->outbox = new_item;
1085 } else if (!folder->draft && !strcasecmp(base, "Drafts")) {
1086 new_item->stype = F_DRAFT;
1087 folder->draft = new_item;
1088 } else if (!folder->queue && !strcasecmp(base, "Queue")) {
1089 new_item->stype = F_QUEUE;
1090 folder->queue = new_item;
1091 } else if (!folder->trash && !strcasecmp(base, "Trash")) {
1092 new_item->stype = F_TRASH;
1093 folder->trash = new_item;
1096 folder_item_append(item, new_item);
1097 if (new_item->no_sub == FALSE)
1098 imap_scan_tree_recursive(session, new_item);
1101 return IMAP_SUCCESS;
1104 static GSList *imap_parse_list(IMAPFolder *folder, IMAPSession *session,
1105 const gchar *real_path, gchar *separator)
1107 gchar buf[IMAPBUFSIZE];
1109 gchar separator_str[16];
1112 gchar *loc_name, *loc_path;
1113 GSList *item_list = NULL;
1115 FolderItem *new_item;
1117 debug_print("getting list of %s ...\n",
1118 *real_path ? real_path : "\"\"");
1120 str = g_string_new(NULL);
1123 if (sock_gets(SESSION(session)->sock, buf, sizeof(buf)) <= 0) {
1124 log_warning(_("error occurred while getting LIST.\n"));
1128 if (buf[0] != '*' || buf[1] != ' ') {
1129 log_print("IMAP4< %s\n", buf);
1132 debug_print("IMAP4< %s\n", buf);
1134 g_string_assign(str, buf);
1136 if (strncmp(p, "LIST ", 5) != 0) continue;
1139 if (*p != '(') continue;
1141 p = strchr_cpy(p, ')', flags, sizeof(flags));
1143 while (*p == ' ') p++;
1145 p = strchr_cpy(p, ' ', separator_str, sizeof(separator_str));
1147 extract_quote(separator_str, '"');
1148 if (!strcmp(separator_str, "NIL"))
1149 separator_str[0] = '\0';
1151 *separator = separator_str[0];
1154 while (*p == ' ') p++;
1155 if (*p == '{' || *p == '"')
1156 p = imap_parse_atom(SESSION(session)->sock, p,
1157 buf, sizeof(buf), str);
1159 strncpy2(buf, p, sizeof(buf));
1160 strtailchomp(buf, separator_str[0]);
1161 if (buf[0] == '\0') continue;
1162 if (!strcmp(buf, real_path)) continue;
1164 if (separator_str[0] != '\0')
1165 subst_char(buf, separator_str[0], '/');
1166 name = g_basename(buf);
1167 if (name[0] == '.') continue;
1169 loc_name = imap_modified_utf7_to_locale(name);
1170 loc_path = imap_modified_utf7_to_locale(buf);
1171 new_item = folder_item_new(FOLDER(folder), loc_name, loc_path);
1172 if (strcasestr(flags, "\\Noinferiors") != NULL)
1173 new_item->no_sub = TRUE;
1174 if (strcmp(buf, "INBOX") != 0 &&
1175 strcasestr(flags, "\\Noselect") != NULL)
1176 new_item->no_select = TRUE;
1178 item_list = g_slist_append(item_list, new_item);
1180 debug_print("folder %s has been added.\n", loc_path);
1185 g_string_free(str, TRUE);
1190 gint imap_create_tree(Folder *folder)
1192 g_return_val_if_fail(folder != NULL, -1);
1193 g_return_val_if_fail(folder->node != NULL, -1);
1194 g_return_val_if_fail(folder->node->data != NULL, -1);
1195 g_return_val_if_fail(folder->account != NULL, -1);
1197 imap_scan_tree(folder);
1198 imap_create_missing_folders(folder);
1203 static void imap_create_missing_folders(Folder *folder)
1205 g_return_if_fail(folder != NULL);
1208 folder->inbox = imap_create_special_folder
1209 (folder, F_INBOX, "INBOX");
1211 if (!folder->outbox)
1212 folder->outbox = imap_create_special_folder
1213 (folder, F_OUTBOX, "Sent");
1215 folder->draft = imap_create_special_folder
1216 (folder, F_DRAFT, "Drafts");
1218 folder->queue = imap_create_special_folder
1219 (folder, F_QUEUE, "Queue");
1222 folder->trash = imap_create_special_folder
1223 (folder, F_TRASH, "Trash");
1226 static FolderItem *imap_create_special_folder(Folder *folder,
1227 SpecialFolderItemType stype,
1231 FolderItem *new_item;
1233 g_return_val_if_fail(folder != NULL, NULL);
1234 g_return_val_if_fail(folder->node != NULL, NULL);
1235 g_return_val_if_fail(folder->node->data != NULL, NULL);
1236 g_return_val_if_fail(folder->account != NULL, NULL);
1237 g_return_val_if_fail(name != NULL, NULL);
1239 item = FOLDER_ITEM(folder->node->data);
1240 new_item = imap_create_folder(folder, item, name);
1243 g_warning("Can't create '%s'\n", name);
1244 if (!folder->inbox) return NULL;
1246 new_item = imap_create_folder(folder, folder->inbox, name);
1248 g_warning("Can't create '%s' under INBOX\n", name);
1250 new_item->stype = stype;
1252 new_item->stype = stype;
1257 static gchar *imap_folder_get_path(Folder *folder)
1261 g_return_val_if_fail(folder != NULL, NULL);
1262 g_return_val_if_fail(folder->account != NULL, NULL);
1264 folder_path = g_strconcat(get_imap_cache_dir(),
1266 folder->account->recv_server,
1268 folder->account->userid,
1274 static gchar *imap_item_get_path(Folder *folder, FolderItem *item)
1276 gchar *folder_path, *path;
1278 g_return_val_if_fail(folder != NULL, NULL);
1279 g_return_val_if_fail(item != NULL, NULL);
1280 folder_path = imap_folder_get_path(folder);
1282 g_return_val_if_fail(folder_path != NULL, NULL);
1283 if (folder_path[0] == G_DIR_SEPARATOR) {
1285 path = g_strconcat(folder_path, G_DIR_SEPARATOR_S,
1288 path = g_strdup(folder_path);
1291 path = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S,
1292 folder_path, G_DIR_SEPARATOR_S,
1295 path = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S,
1298 g_free(folder_path);
1303 FolderItem *imap_create_folder(Folder *folder, FolderItem *parent,
1306 gchar *dirpath, *imap_path;
1307 IMAPSession *session;
1308 FolderItem *new_item;
1314 g_return_val_if_fail(folder != NULL, NULL);
1315 g_return_val_if_fail(folder->account != NULL, NULL);
1316 g_return_val_if_fail(parent != NULL, NULL);
1317 g_return_val_if_fail(name != NULL, NULL);
1319 session = imap_session_get(folder);
1320 if (!session) return NULL;
1322 if (!parent->parent && strcmp(name, "INBOX") == 0)
1323 dirpath = g_strdup(name);
1324 else if (parent->path)
1325 dirpath = g_strconcat(parent->path, "/", name, NULL);
1326 else if ((p = strchr(name, '/')) != NULL && *(p + 1) != '\0')
1327 dirpath = g_strdup(name);
1328 else if (folder->account->imap_dir && *folder->account->imap_dir) {
1331 Xstrdup_a(imap_dir, folder->account->imap_dir, return NULL);
1332 strtailchomp(imap_dir, '/');
1333 dirpath = g_strconcat(imap_dir, "/", name, NULL);
1335 dirpath = g_strdup(name);
1337 /* keep trailing directory separator to create a folder that contains
1339 imap_path = imap_locale_to_modified_utf7(dirpath);
1340 strtailchomp(dirpath, '/');
1341 Xstrdup_a(new_name, name, {g_free(dirpath); return NULL;});
1342 strtailchomp(new_name, '/');
1343 separator = imap_get_path_separator(IMAP_FOLDER(folder), imap_path);
1344 imap_path_separator_subst(imap_path, separator);
1345 subst_char(new_name, '/', separator);
1347 if (strcmp(name, "INBOX") != 0) {
1350 gboolean exist = FALSE;
1352 argbuf = g_ptr_array_new();
1353 ok = imap_cmd_list(session, NULL, imap_path,
1355 if (ok != IMAP_SUCCESS) {
1356 log_warning(_("can't create mailbox: LIST failed\n"));
1359 ptr_array_free_strings(argbuf);
1360 g_ptr_array_free(argbuf, TRUE);
1364 for (i = 0; i < argbuf->len; i++) {
1366 str = g_ptr_array_index(argbuf, i);
1367 if (!strncmp(str, "LIST ", 5)) {
1372 ptr_array_free_strings(argbuf);
1373 g_ptr_array_free(argbuf, TRUE);
1376 ok = imap_cmd_create(session, imap_path);
1377 if (ok != IMAP_SUCCESS) {
1378 log_warning(_("can't create mailbox\n"));
1386 new_item = folder_item_new(folder, new_name, dirpath);
1387 folder_item_append(parent, new_item);
1391 dirpath = folder_item_get_path(new_item);
1392 if (!is_dir_exist(dirpath))
1393 make_dir_hier(dirpath);
1399 gint imap_rename_folder(Folder *folder, FolderItem *item, const gchar *name)
1403 gchar *real_oldpath;
1404 gchar *real_newpath;
1407 gchar *old_cache_dir;
1408 gchar *new_cache_dir;
1409 IMAPSession *session;
1412 gint exists, recent, unseen;
1413 guint32 uid_validity;
1415 g_return_val_if_fail(folder != NULL, -1);
1416 g_return_val_if_fail(item != NULL, -1);
1417 g_return_val_if_fail(item->path != NULL, -1);
1418 g_return_val_if_fail(name != NULL, -1);
1420 session = imap_session_get(folder);
1421 if (!session) return -1;
1423 real_oldpath = imap_get_real_path(IMAP_FOLDER(folder), item->path);
1425 g_free(session->mbox);
1426 session->mbox = NULL;
1427 ok = imap_cmd_examine(session, "INBOX",
1428 &exists, &recent, &unseen, &uid_validity);
1429 if (ok != IMAP_SUCCESS) {
1430 g_free(real_oldpath);
1434 separator = imap_get_path_separator(IMAP_FOLDER(folder), item->path);
1435 if (strchr(item->path, G_DIR_SEPARATOR)) {
1436 dirpath = g_dirname(item->path);
1437 newpath = g_strconcat(dirpath, G_DIR_SEPARATOR_S, name, NULL);
1440 newpath = g_strdup(name);
1442 real_newpath = imap_locale_to_modified_utf7(newpath);
1443 imap_path_separator_subst(real_newpath, separator);
1445 ok = imap_cmd_rename(session, real_oldpath, real_newpath);
1446 if (ok != IMAP_SUCCESS) {
1447 log_warning(_("can't rename mailbox: %s to %s\n"),
1448 real_oldpath, real_newpath);
1449 g_free(real_oldpath);
1451 g_free(real_newpath);
1456 item->name = g_strdup(name);
1458 old_cache_dir = folder_item_get_path(item);
1460 node = g_node_find(item->folder->node, G_PRE_ORDER, G_TRAVERSE_ALL,
1462 paths[0] = g_strdup(item->path);
1464 g_node_traverse(node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
1465 imap_rename_folder_func, paths);
1467 if (is_dir_exist(old_cache_dir)) {
1468 new_cache_dir = folder_item_get_path(item);
1469 if (rename(old_cache_dir, new_cache_dir) < 0) {
1470 FILE_OP_ERROR(old_cache_dir, "rename");
1472 g_free(new_cache_dir);
1475 g_free(old_cache_dir);
1478 g_free(real_oldpath);
1479 g_free(real_newpath);
1484 gint imap_remove_folder(Folder *folder, FolderItem *item)
1487 IMAPSession *session;
1490 gint exists, recent, unseen;
1491 guint32 uid_validity;
1493 g_return_val_if_fail(folder != NULL, -1);
1494 g_return_val_if_fail(item != NULL, -1);
1495 g_return_val_if_fail(item->path != NULL, -1);
1497 session = imap_session_get(folder);
1498 if (!session) return -1;
1500 path = imap_get_real_path(IMAP_FOLDER(folder), item->path);
1502 ok = imap_cmd_examine(session, "INBOX",
1503 &exists, &recent, &unseen, &uid_validity);
1504 if (ok != IMAP_SUCCESS) {
1509 ok = imap_cmd_delete(session, path);
1510 if (ok != IMAP_SUCCESS) {
1511 log_warning(_("can't delete mailbox\n"));
1517 cache_dir = folder_item_get_path(item);
1518 if (is_dir_exist(cache_dir) && remove_dir_recursive(cache_dir) < 0)
1519 g_warning("can't remove directory '%s'\n", cache_dir);
1521 folder_item_remove(item);
1526 static GSList *imap_get_uncached_messages(IMAPSession *session,
1528 MsgNumberList *numlist)
1531 GSList *newlist = NULL;
1532 GSList *llast = NULL;
1537 g_return_val_if_fail(session != NULL, NULL);
1538 g_return_val_if_fail(item != NULL, NULL);
1539 g_return_val_if_fail(item->folder != NULL, NULL);
1540 g_return_val_if_fail(FOLDER_CLASS(item->folder) == &imap_class, NULL);
1542 imapset = numberlist_to_imapset(numlist);
1543 while (imapset != NULL) {
1544 if (imap_cmd_envelope(session, imapset)
1546 log_warning(_("can't get envelope\n"));
1550 str = g_string_new(NULL);
1553 if ((tmp = sock_getline(SESSION(session)->sock)) == NULL) {
1554 log_warning(_("error occurred while getting envelope.\n"));
1555 g_string_free(str, TRUE);
1559 if (tmp[0] != '*' || tmp[1] != ' ') {
1560 log_print("IMAP4< %s\n", tmp);
1564 if (strstr(tmp, "FETCH") == NULL) {
1565 log_print("IMAP4< %s\n", tmp);
1569 log_print("IMAP4< %s\n", tmp);
1570 g_string_assign(str, tmp);
1573 msginfo = imap_parse_envelope
1574 (SESSION(session)->sock, item, str);
1576 log_warning(_("can't parse envelope: %s\n"), str->str);
1579 if (item->stype == F_QUEUE) {
1580 MSG_SET_TMP_FLAGS(msginfo->flags, MSG_QUEUED);
1581 } else if (item->stype == F_DRAFT) {
1582 MSG_SET_TMP_FLAGS(msginfo->flags, MSG_DRAFT);
1585 msginfo->folder = item;
1588 llast = newlist = g_slist_append(newlist, msginfo);
1590 llast = g_slist_append(llast, msginfo);
1591 llast = llast->next;
1595 g_string_free(str, TRUE);
1596 imapset = numberlist_to_imapset(NULL);
1602 static void imap_delete_all_cached_messages(FolderItem *item)
1606 g_return_if_fail(item != NULL);
1607 g_return_if_fail(item->folder != NULL);
1608 g_return_if_fail(FOLDER_CLASS(item->folder) == &imap_class);
1610 debug_print("Deleting all cached messages...\n");
1612 dir = folder_item_get_path(item);
1613 if (is_dir_exist(dir))
1614 remove_all_numbered_files(dir);
1617 debug_print("done.\n");
1621 static SockInfo *imap_open_tunnel(const gchar *server,
1622 const gchar *tunnelcmd,
1625 static SockInfo *imap_open_tunnel(const gchar *server,
1626 const gchar *tunnelcmd)
1631 if ((sock = sock_connect_cmd(server, tunnelcmd)) == NULL) {
1632 log_warning(_("Can't establish IMAP4 session with: %s\n"),
1637 return imap_init_sock(sock, ssl_type);
1639 return imap_init_sock(sock);
1645 static SockInfo *imap_open(const gchar *server, gushort port,
1648 static SockInfo *imap_open(const gchar *server, gushort port)
1653 if ((sock = sock_connect(server, port)) == NULL) {
1654 log_warning(_("Can't connect to IMAP4 server: %s:%d\n"),
1660 if (ssl_type == SSL_TUNNEL && !ssl_init_socket(sock)) {
1661 log_warning(_("Can't establish IMAP4 session with: %s:%d\n"),
1671 static SockInfo *imap_init_sock(SockInfo *sock, SSLType ssl_type)
1673 static SockInfo *imap_init_sock(SockInfo *sock)
1680 static GList *imap_parse_namespace_str(gchar *str)
1685 IMAPNameSpace *namespace;
1686 GList *ns_list = NULL;
1688 while (*p != '\0') {
1689 /* parse ("#foo" "/") */
1691 while (*p && *p != '(') p++;
1692 if (*p == '\0') break;
1695 while (*p && *p != '"') p++;
1696 if (*p == '\0') break;
1700 while (*p && *p != '"') p++;
1701 if (*p == '\0') break;
1705 while (*p && isspace(*p)) p++;
1706 if (*p == '\0') break;
1707 if (strncmp(p, "NIL", 3) == 0)
1709 else if (*p == '"') {
1712 while (*p && *p != '"') p++;
1713 if (*p == '\0') break;
1718 while (*p && *p != ')') p++;
1719 if (*p == '\0') break;
1722 namespace = g_new(IMAPNameSpace, 1);
1723 namespace->name = g_strdup(name);
1724 namespace->separator = separator ? separator[0] : '\0';
1725 ns_list = g_list_append(ns_list, namespace);
1731 static void imap_parse_namespace(IMAPSession *session, IMAPFolder *folder)
1736 g_return_if_fail(session != NULL);
1737 g_return_if_fail(folder != NULL);
1739 if (folder->ns_personal != NULL ||
1740 folder->ns_others != NULL ||
1741 folder->ns_shared != NULL)
1744 if (!imap_has_capability(session, "NAMESPACE")) {
1745 imap_get_namespace_by_list(session, folder);
1749 if (imap_cmd_namespace(session, &ns_str)
1751 log_warning(_("can't get namespace\n"));
1755 str_array = strsplit_parenthesis(ns_str, '(', ')', 3);
1756 if (str_array == NULL) {
1758 imap_get_namespace_by_list(session, folder);
1762 folder->ns_personal = imap_parse_namespace_str(str_array[0]);
1763 if (str_array[0] && str_array[1])
1764 folder->ns_others = imap_parse_namespace_str(str_array[1]);
1765 if (str_array[0] && str_array[1] && str_array[2])
1766 folder->ns_shared = imap_parse_namespace_str(str_array[2]);
1767 g_strfreev(str_array);
1771 static void imap_get_namespace_by_list(IMAPSession *session, IMAPFolder *folder)
1773 GSList *item_list, *cur;
1774 gchar separator = '\0';
1775 IMAPNameSpace *namespace;
1777 g_return_if_fail(session != NULL);
1778 g_return_if_fail(folder != NULL);
1780 if (folder->ns_personal != NULL ||
1781 folder->ns_others != NULL ||
1782 folder->ns_shared != NULL)
1785 imap_gen_send(session, "LIST \"\" \"\"");
1786 item_list = imap_parse_list(folder, session, "", &separator);
1787 for (cur = item_list; cur != NULL; cur = cur->next)
1788 folder_item_destroy(FOLDER_ITEM(cur->data));
1789 g_slist_free(item_list);
1791 namespace = g_new(IMAPNameSpace, 1);
1792 namespace->name = g_strdup("");
1793 namespace->separator = separator;
1794 folder->ns_personal = g_list_append(NULL, namespace);
1797 static IMAPNameSpace *imap_find_namespace_from_list(GList *ns_list,
1800 IMAPNameSpace *namespace = NULL;
1801 gchar *tmp_path, *name;
1803 if (!path) path = "";
1805 Xstrcat_a(tmp_path, path, "/", return NULL);
1807 for (; ns_list != NULL; ns_list = ns_list->next) {
1808 IMAPNameSpace *tmp_ns = ns_list->data;
1810 Xstrdup_a(name, tmp_ns->name, return namespace);
1811 if (tmp_ns->separator && tmp_ns->separator != '/')
1812 subst_char(name, tmp_ns->separator, '/');
1813 if (strncmp(tmp_path, name, strlen(name)) == 0)
1820 static IMAPNameSpace *imap_find_namespace(IMAPFolder *folder,
1823 IMAPNameSpace *namespace;
1825 g_return_val_if_fail(folder != NULL, NULL);
1827 namespace = imap_find_namespace_from_list(folder->ns_personal, path);
1828 if (namespace) return namespace;
1829 namespace = imap_find_namespace_from_list(folder->ns_others, path);
1830 if (namespace) return namespace;
1831 namespace = imap_find_namespace_from_list(folder->ns_shared, path);
1832 if (namespace) return namespace;
1837 static gchar imap_get_path_separator(IMAPFolder *folder, const gchar *path)
1839 IMAPNameSpace *namespace;
1840 gchar separator = '/';
1842 namespace = imap_find_namespace(folder, path);
1843 if (namespace && namespace->separator)
1844 separator = namespace->separator;
1849 static gchar *imap_get_real_path(IMAPFolder *folder, const gchar *path)
1854 g_return_val_if_fail(folder != NULL, NULL);
1855 g_return_val_if_fail(path != NULL, NULL);
1857 real_path = imap_locale_to_modified_utf7(path);
1858 separator = imap_get_path_separator(folder, path);
1859 imap_path_separator_subst(real_path, separator);
1864 static gchar *imap_parse_atom(SockInfo *sock, gchar *src,
1865 gchar *dest, gint dest_len, GString *str)
1867 gchar *cur_pos = src;
1870 g_return_val_if_fail(str != NULL, cur_pos);
1872 /* read the next line if the current response buffer is empty */
1873 while (isspace(*cur_pos)) cur_pos++;
1874 while (*cur_pos == '\0') {
1875 if ((nextline = sock_getline(sock)) == NULL)
1877 g_string_assign(str, nextline);
1879 strretchomp(nextline);
1880 /* log_print("IMAP4< %s\n", nextline); */
1881 debug_print("IMAP4< %s\n", nextline);
1884 while (isspace(*cur_pos)) cur_pos++;
1887 if (!strncmp(cur_pos, "NIL", 3)) {
1890 } else if (*cur_pos == '\"') {
1893 p = get_quoted(cur_pos, '\"', dest, dest_len);
1894 cur_pos = p ? p : cur_pos + 2;
1895 } else if (*cur_pos == '{') {
1900 cur_pos = strchr_cpy(cur_pos + 1, '}', buf, sizeof(buf));
1902 g_return_val_if_fail(len > 0, cur_pos);
1904 g_string_truncate(str, 0);
1908 if ((nextline = sock_getline(sock)) == NULL)
1910 line_len += strlen(nextline);
1911 g_string_append(str, nextline);
1913 strretchomp(nextline);
1914 /* log_print("IMAP4< %s\n", nextline); */
1915 debug_print("IMAP4< %s\n", nextline);
1917 } while (line_len < len);
1919 memcpy(dest, cur_pos, MIN(len, dest_len - 1));
1920 dest[MIN(len, dest_len - 1)] = '\0';
1927 static gchar *imap_get_header(SockInfo *sock, gchar *cur_pos, gchar **headers,
1937 g_return_val_if_fail(str != NULL, cur_pos);
1939 while (isspace(*cur_pos)) cur_pos++;
1941 g_return_val_if_fail(*cur_pos == '{', cur_pos);
1943 cur_pos = strchr_cpy(cur_pos + 1, '}', buf, sizeof(buf));
1945 g_return_val_if_fail(len > 0, cur_pos);
1947 g_string_truncate(str, 0);
1951 if ((nextline = sock_getline(sock)) == NULL)
1953 block_len += strlen(nextline);
1954 g_string_append(str, nextline);
1956 strretchomp(nextline);
1957 /* debug_print("IMAP4< %s\n", nextline); */
1959 } while (block_len < len);
1961 debug_print("IMAP4< [contents of BODY.PEEK[HEADER.FIELDS (...)]]\n");
1963 *headers = g_strndup(cur_pos, len);
1966 while (isspace(*cur_pos)) cur_pos++;
1967 while (*cur_pos == '\0') {
1968 if ((nextline = sock_getline(sock)) == NULL)
1970 g_string_assign(str, nextline);
1972 strretchomp(nextline);
1973 debug_print("IMAP4< %s\n", nextline);
1976 while (isspace(*cur_pos)) cur_pos++;
1982 static MsgFlags imap_parse_flags(const gchar *flag_str)
1984 const gchar *p = flag_str;
1985 MsgFlags flags = {0, 0};
1987 flags.perm_flags = MSG_UNREAD;
1989 while ((p = strchr(p, '\\')) != NULL) {
1992 if (g_strncasecmp(p, "Recent", 6) == 0 && MSG_IS_UNREAD(flags)) {
1993 MSG_SET_PERM_FLAGS(flags, MSG_NEW);
1994 } else if (g_strncasecmp(p, "Seen", 4) == 0) {
1995 MSG_UNSET_PERM_FLAGS(flags, MSG_NEW|MSG_UNREAD);
1996 } else if (g_strncasecmp(p, "Deleted", 7) == 0) {
1997 MSG_SET_PERM_FLAGS(flags, MSG_DELETED);
1998 } else if (g_strncasecmp(p, "Flagged", 7) == 0) {
1999 MSG_SET_PERM_FLAGS(flags, MSG_MARKED);
2000 } else if (g_strncasecmp(p, "Answered", 8) == 0) {
2001 MSG_SET_PERM_FLAGS(flags, MSG_REPLIED);
2008 static MsgInfo *imap_parse_envelope(SockInfo *sock, FolderItem *item,
2011 gchar buf[IMAPBUFSIZE];
2012 MsgInfo *msginfo = NULL;
2017 MsgFlags flags = {0, 0}, imap_flags = {0, 0};
2019 g_return_val_if_fail(line_str != NULL, NULL);
2020 g_return_val_if_fail(line_str->str[0] == '*' &&
2021 line_str->str[1] == ' ', NULL);
2023 MSG_SET_TMP_FLAGS(flags, MSG_IMAP);
2024 if (item->stype == F_QUEUE) {
2025 MSG_SET_TMP_FLAGS(flags, MSG_QUEUED);
2026 } else if (item->stype == F_DRAFT) {
2027 MSG_SET_TMP_FLAGS(flags, MSG_DRAFT);
2030 cur_pos = line_str->str + 2;
2032 #define PARSE_ONE_ELEMENT(ch) \
2034 cur_pos = strchr_cpy(cur_pos, ch, buf, sizeof(buf)); \
2035 if (cur_pos == NULL) { \
2036 g_warning("cur_pos == NULL\n"); \
2037 procmsg_msginfo_free(msginfo); \
2042 PARSE_ONE_ELEMENT(' ');
2045 PARSE_ONE_ELEMENT(' ');
2046 g_return_val_if_fail(!strcmp(buf, "FETCH"), NULL);
2048 g_return_val_if_fail(*cur_pos == '(', NULL);
2051 while (*cur_pos != '\0' && *cur_pos != ')') {
2052 while (*cur_pos == ' ') cur_pos++;
2054 if (!strncmp(cur_pos, "UID ", 4)) {
2056 uid = strtoul(cur_pos, &cur_pos, 10);
2057 } else if (!strncmp(cur_pos, "FLAGS ", 6)) {
2059 if (*cur_pos != '(') {
2060 g_warning("*cur_pos != '('\n");
2061 procmsg_msginfo_free(msginfo);
2065 PARSE_ONE_ELEMENT(')');
2066 imap_flags = imap_parse_flags(buf);
2067 } else if (!strncmp(cur_pos, "RFC822.SIZE ", 12)) {
2069 size = strtol(cur_pos, &cur_pos, 10);
2070 } else if (!strncmp(cur_pos, "BODY[HEADER.FIELDS ", 19)) {
2074 if (*cur_pos != '(') {
2075 g_warning("*cur_pos != '('\n");
2076 procmsg_msginfo_free(msginfo);
2080 PARSE_ONE_ELEMENT(')');
2081 if (*cur_pos != ']') {
2082 g_warning("*cur_pos != ']'\n");
2083 procmsg_msginfo_free(msginfo);
2088 cur_pos = imap_get_header(sock, cur_pos, &headers,
2090 msginfo = procheader_parse_str(headers, flags, FALSE, FALSE);
2093 g_warning("invalid FETCH response: %s\n", cur_pos);
2099 msginfo->msgnum = uid;
2100 msginfo->size = size;
2101 msginfo->flags.tmp_flags |= imap_flags.tmp_flags;
2102 msginfo->flags.perm_flags = imap_flags.perm_flags;
2108 static gint imap_set_message_flags(IMAPSession *session,
2109 MsgNumberList *numlist,
2117 buf = g_string_new(is_set ? "+FLAGS (" : "-FLAGS (");
2119 if (IMAP_IS_SEEN(flags)) g_string_append(buf, "\\Seen ");
2120 if (IMAP_IS_ANSWERED(flags)) g_string_append(buf, "\\Answered ");
2121 if (IMAP_IS_FLAGGED(flags)) g_string_append(buf, "\\Flagged ");
2122 if (IMAP_IS_DELETED(flags)) g_string_append(buf, "\\Deleted ");
2123 if (IMAP_IS_DRAFT(flags)) g_string_append(buf, "\\Draft");
2125 if (buf->str[buf->len - 1] == ' ')
2126 g_string_truncate(buf, buf->len - 1);
2128 g_string_append_c(buf, ')');
2130 imapset = numberlist_to_imapset(numlist);
2131 while (imapset != NULL) {
2132 ok = imap_cmd_store(session, imapset,
2134 imapset = numberlist_to_imapset(NULL);
2136 g_string_free(buf, TRUE);
2141 static gint imap_select(IMAPSession *session, IMAPFolder *folder,
2143 gint *exists, gint *recent, gint *unseen,
2144 guint32 *uid_validity)
2148 gint exists_, recent_, unseen_, uid_validity_;
2150 if (!exists || !recent || !unseen || !uid_validity) {
2151 if (session->mbox && strcmp(session->mbox, path) == 0)
2152 return IMAP_SUCCESS;
2156 uid_validity = &uid_validity_;
2159 g_free(session->mbox);
2160 session->mbox = NULL;
2162 real_path = imap_get_real_path(folder, path);
2163 ok = imap_cmd_select(session, real_path,
2164 exists, recent, unseen, uid_validity);
2165 if (ok != IMAP_SUCCESS)
2166 log_warning(_("can't select folder: %s\n"), real_path);
2168 session->mbox = g_strdup(path);
2169 session->folder_content_changed = FALSE;
2176 #define THROW(err) { ok = err; goto catch; }
2178 static gint imap_status(IMAPSession *session, IMAPFolder *folder,
2180 gint *messages, gint *recent,
2181 guint32 *uid_next, guint32 *uid_validity,
2190 *messages = *recent = *uid_next = *uid_validity = *unseen = 0;
2192 argbuf = g_ptr_array_new();
2194 real_path = imap_get_real_path(folder, path);
2195 QUOTE_IF_REQUIRED(real_path_, real_path);
2196 imap_gen_send(session, "STATUS %s "
2197 "(MESSAGES RECENT UIDNEXT UIDVALIDITY UNSEEN)",
2200 ok = imap_cmd_ok(session, argbuf);
2201 if (ok != IMAP_SUCCESS) THROW(ok);
2203 str = search_array_str(argbuf, "STATUS");
2204 if (!str) THROW(IMAP_ERROR);
2206 str = strchr(str, '(');
2207 if (!str) THROW(IMAP_ERROR);
2209 while (*str != '\0' && *str != ')') {
2210 while (*str == ' ') str++;
2212 if (!strncmp(str, "MESSAGES ", 9)) {
2214 *messages = strtol(str, &str, 10);
2215 } else if (!strncmp(str, "RECENT ", 7)) {
2217 *recent = strtol(str, &str, 10);
2218 } else if (!strncmp(str, "UIDNEXT ", 8)) {
2220 *uid_next = strtoul(str, &str, 10);
2221 } else if (!strncmp(str, "UIDVALIDITY ", 12)) {
2223 *uid_validity = strtoul(str, &str, 10);
2224 } else if (!strncmp(str, "UNSEEN ", 7)) {
2226 *unseen = strtol(str, &str, 10);
2228 g_warning("invalid STATUS response: %s\n", str);
2235 ptr_array_free_strings(argbuf);
2236 g_ptr_array_free(argbuf, TRUE);
2244 /* low-level IMAP4rev1 commands */
2246 static gint imap_cmd_authenticate(IMAPSession *session, const gchar *user,
2247 const gchar *pass, IMAPAuthType type)
2254 gchar hexdigest[33];
2258 auth_type = "CRAM-MD5";
2260 imap_gen_send(session, "AUTHENTICATE %s", auth_type);
2261 ok = imap_gen_recv(session, &buf);
2262 if (ok != IMAP_SUCCESS || buf[0] != '+' || buf[1] != ' ') {
2267 challenge = g_malloc(strlen(buf + 2) + 1);
2268 challenge_len = base64_decode(challenge, buf + 2, -1);
2269 challenge[challenge_len] = '\0';
2270 log_print("IMAP< [Decoded: %s]\n", challenge);
2273 md5_hex_hmac(hexdigest, challenge, challenge_len, pass, strlen(pass));
2276 response = g_strdup_printf("%s %s", user, hexdigest);
2277 log_print("IMAP> [Encoded: %s]\n", response);
2278 response64 = g_malloc((strlen(response) + 3) * 2 + 1);
2279 base64_encode(response64, response, strlen(response));
2282 log_print("IMAP> %s\n", response64);
2283 sock_puts(SESSION(session)->sock, response64);
2284 ok = imap_cmd_ok(session, NULL);
2285 if (ok != IMAP_SUCCESS)
2286 log_warning(_("IMAP4 authentication failed.\n"));
2291 static gint imap_cmd_login(IMAPSession *session,
2292 const gchar *user, const gchar *pass)
2294 gchar *user_, *pass_;
2297 QUOTE_IF_REQUIRED(user_, user);
2298 QUOTE_IF_REQUIRED(pass_, pass);
2299 imap_gen_send(session, "LOGIN %s %s", user_, pass_);
2301 ok = imap_cmd_ok(session, NULL);
2302 if (ok != IMAP_SUCCESS)
2303 log_warning(_("IMAP4 login failed.\n"));
2308 static gint imap_cmd_logout(IMAPSession *session)
2310 imap_gen_send(session, "LOGOUT");
2311 return imap_cmd_ok(session, NULL);
2314 /* Send CAPABILITY, and examine the server's response to see whether this
2315 * connection is pre-authenticated or not and build a list of CAPABILITIES. */
2316 static gint imap_greeting(IMAPSession *session)
2321 imap_gen_send(session, "CAPABILITY");
2323 argbuf = g_ptr_array_new();
2325 if (imap_cmd_ok(session, argbuf) != IMAP_SUCCESS ||
2326 ((capstr = search_array_str(argbuf, "CAPABILITY ")) == NULL)) {
2327 ptr_array_free_strings(argbuf);
2328 g_ptr_array_free(argbuf, TRUE);
2332 session->authenticated = search_array_str(argbuf, "PREAUTH") != NULL;
2334 capstr += strlen("CAPABILITY ");
2336 IMAP_SESSION(session)->capability = g_strsplit(capstr, " ", 0);
2338 ptr_array_free_strings(argbuf);
2339 g_ptr_array_free(argbuf, TRUE);
2344 static gboolean imap_has_capability(IMAPSession *session, const gchar *cap)
2348 for (p = session->capability; *p != NULL; ++p)
2349 if (g_strcasecmp(*p, cap) == 0)
2355 void imap_free_capabilities(IMAPSession *session)
2357 g_strfreev(session->capability);
2358 session->capability = NULL;
2361 static const IMAPSet numberlist_to_imapset(MsgNumberList *list)
2363 static GString *imapset = NULL;
2364 static MsgNumberList *numlist, *elem;
2365 guint first, last, next;
2367 if (imapset == NULL)
2368 imapset = g_string_sized_new(256);
2370 g_string_truncate(imapset, 0);
2373 g_slist_free(numlist);
2374 numlist = g_slist_copy(list);
2375 numlist = g_slist_sort(numlist, g_int_compare);
2376 } else if (numlist == NULL) {
2380 first = GPOINTER_TO_INT(numlist->data);
2382 for(elem = g_slist_next(numlist); elem != NULL; elem = g_slist_next(elem)) {
2383 next = GPOINTER_TO_INT(elem->data);
2385 if(next != (last + 1)) {
2386 if (imapset->len > 0)
2387 g_string_append(imapset, ",");
2389 g_string_sprintfa(imapset, "%d", first);
2391 g_string_sprintfa(imapset, "%d:%d", first, last);
2393 if (imapset->len > IMAPCMDLIMIT) {
2403 if (imapset->len > 0)
2404 g_string_append(imapset, ",");
2406 g_string_sprintfa(imapset, "%d", first);
2408 g_string_sprintfa(imapset, "%d:%d", first, last);
2410 g_slist_free(numlist);
2413 MsgNumberList *remaining;
2415 remaining = elem->next;
2416 remaining = g_slist_prepend(remaining, elem->data);
2418 g_slist_free(numlist);
2419 numlist = remaining;
2422 return imapset->str;
2425 static gint imap_cmd_noop(IMAPSession *session)
2427 imap_gen_send(session, "NOOP");
2428 return imap_cmd_ok(session, NULL);
2431 static gint imap_cmd_starttls(IMAPSession *session)
2433 imap_gen_send(session, "STARTTLS");
2434 return imap_cmd_ok(session, NULL);
2437 #define THROW(err) { ok = err; goto catch; }
2439 static gint imap_cmd_namespace(IMAPSession *session, gchar **ns_str)
2445 argbuf = g_ptr_array_new();
2447 imap_gen_send(session, "NAMESPACE");
2448 if ((ok = imap_cmd_ok(session, argbuf)) != IMAP_SUCCESS) THROW(ok);
2450 str = search_array_str(argbuf, "NAMESPACE");
2451 if (!str) THROW(IMAP_ERROR);
2453 *ns_str = g_strdup(str);
2456 ptr_array_free_strings(argbuf);
2457 g_ptr_array_free(argbuf, TRUE);
2464 static gint imap_cmd_list(IMAPSession *session, const gchar *ref,
2465 const gchar *mailbox, GPtrArray *argbuf)
2467 gchar *ref_, *mailbox_;
2469 if (!ref) ref = "\"\"";
2470 if (!mailbox) mailbox = "\"\"";
2472 QUOTE_IF_REQUIRED(ref_, ref);
2473 QUOTE_IF_REQUIRED(mailbox_, mailbox);
2474 imap_gen_send(session, "LIST %s %s", ref_, mailbox_);
2476 return imap_cmd_ok(session, argbuf);
2479 #define THROW goto catch
2481 static gint imap_cmd_do_select(IMAPSession *session, const gchar *folder,
2483 gint *exists, gint *recent, gint *unseen,
2484 guint32 *uid_validity)
2492 *exists = *recent = *unseen = *uid_validity = 0;
2493 argbuf = g_ptr_array_new();
2496 select_cmd = "EXAMINE";
2498 select_cmd = "SELECT";
2500 QUOTE_IF_REQUIRED(folder_, folder);
2501 imap_gen_send(session, "%s %s", select_cmd, folder_);
2503 if ((ok = imap_cmd_ok(session, argbuf)) != IMAP_SUCCESS) THROW;
2505 resp_str = search_array_contain_str(argbuf, "EXISTS");
2507 if (sscanf(resp_str,"%d EXISTS", exists) != 1) {
2508 g_warning("imap_cmd_select(): invalid EXISTS line.\n");
2513 resp_str = search_array_contain_str(argbuf, "RECENT");
2515 if (sscanf(resp_str, "%d RECENT", recent) != 1) {
2516 g_warning("imap_cmd_select(): invalid RECENT line.\n");
2521 resp_str = search_array_contain_str(argbuf, "UIDVALIDITY");
2523 if (sscanf(resp_str, "OK [UIDVALIDITY %u] ", uid_validity)
2525 g_warning("imap_cmd_select(): invalid UIDVALIDITY line.\n");
2530 resp_str = search_array_contain_str(argbuf, "UNSEEN");
2532 if (sscanf(resp_str, "OK [UNSEEN %d] ", unseen) != 1) {
2533 g_warning("imap_cmd_select(): invalid UNSEEN line.\n");
2539 ptr_array_free_strings(argbuf);
2540 g_ptr_array_free(argbuf, TRUE);
2545 static gint imap_cmd_select(IMAPSession *session, const gchar *folder,
2546 gint *exists, gint *recent, gint *unseen,
2547 guint32 *uid_validity)
2549 return imap_cmd_do_select(session, folder, FALSE,
2550 exists, recent, unseen, uid_validity);
2553 static gint imap_cmd_examine(IMAPSession *session, const gchar *folder,
2554 gint *exists, gint *recent, gint *unseen,
2555 guint32 *uid_validity)
2557 return imap_cmd_do_select(session, folder, TRUE,
2558 exists, recent, unseen, uid_validity);
2563 static gint imap_cmd_create(IMAPSession *session, const gchar *folder)
2567 QUOTE_IF_REQUIRED(folder_, folder);
2568 imap_gen_send(session, "CREATE %s", folder_);
2570 return imap_cmd_ok(session, NULL);
2573 static gint imap_cmd_rename(IMAPSession *session, const gchar *old_folder,
2574 const gchar *new_folder)
2576 gchar *old_folder_, *new_folder_;
2578 QUOTE_IF_REQUIRED(old_folder_, old_folder);
2579 QUOTE_IF_REQUIRED(new_folder_, new_folder);
2580 imap_gen_send(session, "RENAME %s %s", old_folder_, new_folder_);
2582 return imap_cmd_ok(session, NULL);
2585 static gint imap_cmd_delete(IMAPSession *session, const gchar *folder)
2589 QUOTE_IF_REQUIRED(folder_, folder);
2590 imap_gen_send(session, "DELETE %s", folder_);
2592 return imap_cmd_ok(session, NULL);
2595 static gint imap_cmd_search(IMAPSession *session, const gchar *criteria, GSList **list)
2601 g_return_val_if_fail(criteria != NULL, IMAP_ERROR);
2602 g_return_val_if_fail(list != NULL, IMAP_ERROR);
2606 argbuf = g_ptr_array_new();
2607 imap_gen_send(session, "UID SEARCH %s", criteria);
2609 ok = imap_cmd_ok(session, argbuf);
2610 if (ok != IMAP_SUCCESS) {
2611 ptr_array_free_strings(argbuf);
2612 g_ptr_array_free(argbuf, TRUE);
2616 if ((uidlist = search_array_str(argbuf, "SEARCH ")) != NULL) {
2617 gchar **strlist, **p;
2619 strlist = g_strsplit(uidlist + 7, " ", 0);
2620 for (p = strlist; *p != NULL; ++p) {
2623 if (sscanf(*p, "%d", &msgnum) == 1)
2624 *list = g_slist_append(*list, GINT_TO_POINTER(msgnum));
2626 g_strfreev(strlist);
2628 ptr_array_free_strings(argbuf);
2629 g_ptr_array_free(argbuf, TRUE);
2631 return IMAP_SUCCESS;
2634 static gint imap_cmd_fetch(IMAPSession *session, guint32 uid, const gchar *filename)
2642 g_return_val_if_fail(filename != NULL, IMAP_ERROR);
2644 imap_gen_send(session, "UID FETCH %d BODY.PEEK[]", uid);
2646 while ((ok = imap_gen_recv(session, &buf))
2648 if (buf[0] != '*' || buf[1] != ' ') {
2652 if (strstr(buf, "FETCH") != NULL)
2656 if (ok != IMAP_SUCCESS) {
2661 cur_pos = strchr(buf, '{');
2662 if (cur_pos == NULL) {
2667 cur_pos = strchr_cpy(cur_pos + 1, '}', size_str, sizeof(size_str));
2668 if (cur_pos == NULL) {
2672 size_num = atol(size_str);
2673 g_return_val_if_fail(size_num > 0, IMAP_ERROR);
2675 if (*cur_pos != '\0') {
2680 if (recv_bytes_write_to_file(SESSION(session)->sock, size_num, filename) != 0) {
2685 if (imap_gen_recv(session, &buf) != IMAP_SUCCESS) {
2690 if (buf[0] == '\0' || buf[strlen(buf) - 1] != ')') {
2696 ok = imap_cmd_ok(session, NULL);
2701 static gint imap_cmd_append(IMAPSession *session, const gchar *destfolder,
2702 const gchar *file, gint32 *new_uid)
2707 gchar buf[BUFFSIZE], *imapbuf;
2712 g_return_val_if_fail(file != NULL, IMAP_ERROR);
2714 size = get_file_size_as_crlf(file);
2715 if ((fp = fopen(file, "rb")) == NULL) {
2716 FILE_OP_ERROR(file, "fopen");
2719 QUOTE_IF_REQUIRED(destfolder_, destfolder);
2720 imap_gen_send(session, "APPEND %s (\\Seen) {%d}", destfolder_, size);
2722 ok = imap_gen_recv(session, &imapbuf);
2723 if (ok != IMAP_SUCCESS || imapbuf[0] != '+' || imapbuf[1] != ' ') {
2724 log_warning(_("can't append %s to %s\n"), file, destfolder_);
2731 log_print("IMAP4> %s\n", _("(sending file...)"));
2733 while (fgets(buf, sizeof(buf), fp) != NULL) {
2735 if (sock_puts(SESSION(session)->sock, buf) < 0) {
2742 FILE_OP_ERROR(file, "fgets");
2747 sock_puts(SESSION(session)->sock, "");
2751 reply = g_ptr_array_new();
2754 ok = imap_cmd_ok(session, reply);
2755 if (ok != IMAP_SUCCESS)
2756 log_warning(_("can't append message to %s\n"), destfolder_);
2758 (new_uid != NULL) &&
2759 (imap_has_capability(session, "UIDPLUS") && reply->len > 0) &&
2760 ((okmsginfo = g_ptr_array_index(reply, reply->len - 1)) != NULL) &&
2761 (sscanf(okmsginfo, "%*u OK [APPENDUID %*u %u]", &newuid) == 1)) {
2765 ptr_array_free_strings(reply);
2766 g_ptr_array_free(reply, TRUE);
2771 static gint imap_cmd_copy(IMAPSession * session,
2773 const gchar * destfolder, gint32 * new_uid)
2776 gint32 olduid, newuid;
2781 g_return_val_if_fail(destfolder != NULL, IMAP_ERROR);
2782 g_return_val_if_fail(session != NULL, IMAP_ERROR);
2783 g_return_val_if_fail(new_uid != NULL, IMAP_ERROR);
2785 QUOTE_IF_REQUIRED(destfolder_, destfolder);
2786 imap_gen_send(session, "UID COPY %d %s", msgnum, destfolder_);
2788 reply = g_ptr_array_new();
2791 ok = imap_cmd_ok(session, reply);
2792 if (ok != IMAP_SUCCESS)
2793 log_warning(_("can't copy %d to %s\n"), msgnum, destfolder_);
2794 else if (imap_has_capability(session, "UIDPLUS") && reply->len > 0)
2795 if ((okmsginfo = g_ptr_array_index(reply, reply->len - 1)) != NULL &&
2796 sscanf(okmsginfo, "%*u OK [COPYUID %*u %u %u]", &olduid, &newuid) == 2 &&
2800 ptr_array_free_strings(reply);
2801 g_ptr_array_free(reply, TRUE);
2805 gint imap_cmd_envelope(IMAPSession *session, IMAPSet set)
2807 static GString *header_fields = NULL;
2809 if (header_fields == NULL) {
2810 const HeaderEntry *headers, *elem;
2812 headers = procheader_get_headernames(FALSE);
2813 header_fields = g_string_new("");
2815 for (elem = headers; elem->name != NULL; ++elem) {
2816 gint namelen = strlen(elem->name);
2818 /* Header fields ending with space are not rfc822 headers */
2819 if (elem->name[namelen - 1] == ' ')
2822 /* strip : at the of header field */
2823 if(elem->name[namelen - 1] == ':')
2829 g_string_sprintfa(header_fields, "%s%.*s",
2830 header_fields->str[0] != '\0' ? " " : "",
2831 namelen, elem->name);
2836 (session, "UID FETCH %s (UID FLAGS RFC822.SIZE BODY.PEEK[HEADER.FIELDS (%s)])",
2837 set, header_fields->str);
2839 return IMAP_SUCCESS;
2842 static gint imap_cmd_store(IMAPSession *session, IMAPSet set,
2847 imap_gen_send(session, "UID STORE %s %s",
2850 if ((ok = imap_cmd_ok(session, NULL)) != IMAP_SUCCESS) {
2851 log_warning(_("error while imap command: STORE %s %s\n"),
2856 return IMAP_SUCCESS;
2859 static gint imap_cmd_expunge(IMAPSession *session)
2863 imap_gen_send(session, "EXPUNGE");
2864 if ((ok = imap_cmd_ok(session, NULL)) != IMAP_SUCCESS) {
2865 log_warning(_("error while imap command: EXPUNGE\n"));
2869 return IMAP_SUCCESS;
2872 static gint imap_cmd_ok(IMAPSession *session, GPtrArray *argbuf)
2874 gint ok = IMAP_SUCCESS;
2879 while ((ok = imap_gen_recv(session, &buf))
2881 // make sure data is long enough for any substring of buf
2882 data = alloca(strlen(buf) + 1);
2884 // untagged line read
2885 if (buf[0] == '*' && buf[1] == ' ') {
2888 g_ptr_array_add(argbuf, g_strdup(buf + 2));
2890 if (sscanf(buf + 2, "%d %s", &num, data) >= 2) {
2891 if (!strcmp(data, "EXISTS")) {
2892 session->exists = num;
2893 session->folder_content_changed = TRUE;
2896 if(!strcmp(data, "EXPUNGE")) {
2898 session->folder_content_changed = TRUE;
2901 // tagged line with correct tag and OK response found
2902 } else if ((sscanf(buf, "%d %s", &cmd_num, data) >= 2) &&
2903 (cmd_num == session->cmd_count) &&
2904 !strcmp(data, "OK")) {
2906 g_ptr_array_add(argbuf, g_strdup(buf));
2920 static void imap_gen_send(IMAPSession *session, const gchar *format, ...)
2927 va_start(args, format);
2928 tmp = g_strdup_vprintf(format, args);
2931 session->cmd_count++;
2933 buf = g_strdup_printf("%d %s\r\n", session->cmd_count, tmp);
2934 if (!strncasecmp(tmp, "LOGIN ", 6) && (p = strchr(tmp + 6, ' '))) {
2936 log_print("IMAP4> %d %s ********\n", session->cmd_count, tmp);
2938 log_print("IMAP4> %d %s\n", session->cmd_count, tmp);
2940 sock_write_all(SESSION(session)->sock, buf, strlen(buf));
2945 static gint imap_gen_recv(IMAPSession *session, gchar **buf)
2947 if ((*buf = sock_getline(SESSION(session)->sock)) == NULL)
2952 log_print("IMAP4< %s\n", *buf);
2954 return IMAP_SUCCESS;
2958 /* misc utility functions */
2960 static gchar *strchr_cpy(const gchar *src, gchar ch, gchar *dest, gint len)
2965 tmp = strchr(src, ch);
2969 memcpy(dest, src, MIN(tmp - src, len - 1));
2970 dest[MIN(tmp - src, len - 1)] = '\0';
2975 static gchar *get_quoted(const gchar *src, gchar ch, gchar *dest, gint len)
2977 const gchar *p = src;
2980 g_return_val_if_fail(*p == ch, NULL);
2985 while (*p != '\0' && *p != ch) {
2987 if (*p == '\\' && *(p + 1) != '\0')
2996 return (gchar *)(*p == ch ? p + 1 : p);
2999 static gchar *search_array_contain_str(GPtrArray *array, const gchar *str)
3003 for (i = 0; i < array->len; i++) {
3006 tmp = g_ptr_array_index(array, i);
3007 if (strstr(tmp, str) != NULL)
3014 static gchar *search_array_str(GPtrArray *array, const gchar *str)
3021 for (i = 0; i < array->len; i++) {
3024 tmp = g_ptr_array_index(array, i);
3025 if (!strncmp(tmp, str, len))
3032 static void imap_path_separator_subst(gchar *str, gchar separator)
3035 gboolean in_escape = FALSE;
3037 if (!separator || separator == '/') return;
3039 for (p = str; *p != '\0'; p++) {
3040 if (*p == '/' && !in_escape)
3042 else if (*p == '&' && *(p + 1) != '-' && !in_escape)
3044 else if (*p == '-' && in_escape)
3049 static gchar *imap_modified_utf7_to_locale(const gchar *mutf7_str)
3052 const gchar *from_p;
3055 to = g_malloc(strlen(mutf7_str) + 1);
3058 for (from_p = mutf7_str; *from_p != '\0'; from_p++) {
3059 if (*from_p == '&' && *(from_p + 1) == '-') {
3069 static iconv_t cd = (iconv_t)-1;
3070 static gboolean iconv_ok = TRUE;
3073 size_t norm_utf7_len;
3075 gchar *to_str, *to_p;
3077 gboolean in_escape = FALSE;
3079 if (!iconv_ok) return g_strdup(mutf7_str);
3081 if (cd == (iconv_t)-1) {
3082 cd = iconv_open(conv_get_current_charset_str(), "UTF-7");
3083 if (cd == (iconv_t)-1) {
3084 g_warning("iconv cannot convert UTF-7 to %s\n",
3085 conv_get_current_charset_str());
3087 return g_strdup(mutf7_str);
3091 norm_utf7 = g_string_new(NULL);
3093 for (p = mutf7_str; *p != '\0'; p++) {
3094 /* replace: '&' -> '+',
3096 escaped ',' -> '/' */
3097 if (!in_escape && *p == '&') {
3098 if (*(p + 1) != '-') {
3099 g_string_append_c(norm_utf7, '+');
3102 g_string_append_c(norm_utf7, '&');
3105 } else if (in_escape && *p == ',') {
3106 g_string_append_c(norm_utf7, '/');
3107 } else if (in_escape && *p == '-') {
3108 g_string_append_c(norm_utf7, '-');
3111 g_string_append_c(norm_utf7, *p);
3115 norm_utf7_p = norm_utf7->str;
3116 norm_utf7_len = norm_utf7->len;
3117 to_len = strlen(mutf7_str) * 5;
3118 to_p = to_str = g_malloc(to_len + 1);
3120 if (iconv(cd, (ICONV_CONST gchar **)&norm_utf7_p, &norm_utf7_len,
3121 &to_p, &to_len) == -1) {
3122 g_warning(_("iconv cannot convert UTF-7 to %s\n"),
3123 conv_get_current_charset_str());
3124 g_string_free(norm_utf7, TRUE);
3126 return g_strdup(mutf7_str);
3129 /* second iconv() call for flushing */
3130 iconv(cd, NULL, NULL, &to_p, &to_len);
3131 g_string_free(norm_utf7, TRUE);
3135 #endif /* !HAVE_ICONV */
3138 static gchar *imap_locale_to_modified_utf7(const gchar *from)
3141 const gchar *from_p;
3144 to = g_malloc(strlen(from) * 2 + 1);
3147 for (from_p = from; *from_p != '\0'; from_p++) {
3148 if (*from_p == '&') {
3158 static iconv_t cd = (iconv_t)-1;
3159 static gboolean iconv_ok = TRUE;
3160 gchar *norm_utf7, *norm_utf7_p;
3161 size_t from_len, norm_utf7_len;
3163 gchar *from_tmp, *to, *p;
3164 gboolean in_escape = FALSE;
3166 if (!iconv_ok) return g_strdup(from);
3168 if (cd == (iconv_t)-1) {
3169 cd = iconv_open("UTF-7", conv_get_current_charset_str());
3170 if (cd == (iconv_t)-1) {
3171 g_warning("iconv cannot convert %s to UTF-7\n",
3172 conv_get_current_charset_str());
3174 return g_strdup(from);
3178 Xstrdup_a(from_tmp, from, return g_strdup(from));
3179 from_len = strlen(from);
3180 norm_utf7_len = from_len * 5;
3181 Xalloca(norm_utf7, norm_utf7_len + 1, return g_strdup(from));
3182 norm_utf7_p = norm_utf7;
3184 #define IS_PRINT(ch) (isprint(ch) && isascii(ch))
3186 while (from_len > 0) {
3187 if (*from_tmp == '+') {
3188 *norm_utf7_p++ = '+';
3189 *norm_utf7_p++ = '-';
3193 } else if (IS_PRINT(*from_tmp)) {
3194 /* printable ascii char */
3195 *norm_utf7_p = *from_tmp;
3201 size_t mb_len = 0, conv_len = 0;
3203 /* unprintable char: convert to UTF-7 */
3205 while (!IS_PRINT(*p) && conv_len < from_len) {
3206 mb_len = mblen(p, MB_LEN_MAX);
3208 g_warning("wrong multibyte sequence\n");
3209 return g_strdup(from);
3215 from_len -= conv_len;
3216 if (iconv(cd, (ICONV_CONST gchar **)&from_tmp,
3218 &norm_utf7_p, &norm_utf7_len) == -1) {
3219 g_warning("iconv cannot convert %s to UTF-7\n",
3220 conv_get_current_charset_str());
3221 return g_strdup(from);
3224 /* second iconv() call for flushing */
3225 iconv(cd, NULL, NULL, &norm_utf7_p, &norm_utf7_len);
3231 *norm_utf7_p = '\0';
3232 to_str = g_string_new(NULL);
3233 for (p = norm_utf7; p < norm_utf7_p; p++) {
3234 /* replace: '&' -> "&-",
3237 BASE64 '/' -> ',' */
3238 if (!in_escape && *p == '&') {
3239 g_string_append(to_str, "&-");
3240 } else if (!in_escape && *p == '+') {
3241 if (*(p + 1) == '-') {
3242 g_string_append_c(to_str, '+');
3245 g_string_append_c(to_str, '&');
3248 } else if (in_escape && *p == '/') {
3249 g_string_append_c(to_str, ',');
3250 } else if (in_escape && *p == '-') {
3251 g_string_append_c(to_str, '-');
3254 g_string_append_c(to_str, *p);
3260 g_string_append_c(to_str, '-');
3264 g_string_free(to_str, FALSE);
3267 #endif /* !HAVE_ICONV */
3270 static gboolean imap_rename_folder_func(GNode *node, gpointer data)
3272 FolderItem *item = node->data;
3273 gchar **paths = data;
3274 const gchar *oldpath = paths[0];
3275 const gchar *newpath = paths[1];
3277 gchar *new_itempath;
3280 oldpathlen = strlen(oldpath);
3281 if (strncmp(oldpath, item->path, oldpathlen) != 0) {
3282 g_warning("path doesn't match: %s, %s\n", oldpath, item->path);
3286 base = item->path + oldpathlen;
3287 while (*base == G_DIR_SEPARATOR) base++;
3289 new_itempath = g_strdup(newpath);
3291 new_itempath = g_strconcat(newpath, G_DIR_SEPARATOR_S, base,
3294 item->path = new_itempath;
3299 static gint get_list_of_uids(Folder *folder, IMAPFolderItem *item, GSList **msgnum_list)
3301 gint ok, nummsgs = 0, lastuid_old;
3302 IMAPSession *session;
3303 GSList *uidlist, *elem;
3306 session = imap_session_get(folder);
3307 g_return_val_if_fail(session != NULL, -1);
3309 ok = imap_select(session, IMAP_FOLDER(folder), item->item.path,
3310 NULL, NULL, NULL, NULL);
3311 if (ok != IMAP_SUCCESS)
3314 cmd_buf = g_strdup_printf("UID %d:*", item->lastuid + 1);
3315 ok = imap_cmd_search(session, cmd_buf, &uidlist);
3318 if (ok == IMAP_SOCKET) {
3319 session_destroy((Session *)session);
3320 ((RemoteFolder *)folder)->session = NULL;
3324 if (ok != IMAP_SUCCESS) {
3328 argbuf = g_ptr_array_new();
3330 cmd_buf = g_strdup_printf("UID FETCH %d:* (UID)", item->lastuid + 1);
3331 imap_gen_send(session, cmd_buf);
3333 ok = imap_cmd_ok(session, argbuf);
3334 if (ok != IMAP_SUCCESS) {
3335 ptr_array_free_strings(argbuf);
3336 g_ptr_array_free(argbuf, TRUE);
3340 for(i = 0; i < argbuf->len; i++) {
3343 if((ret = sscanf(g_ptr_array_index(argbuf, i),
3344 "%*d FETCH (UID %d)", &msgnum)) == 1)
3345 uidlist = g_slist_prepend(uidlist, GINT_TO_POINTER(msgnum));
3347 ptr_array_free_strings(argbuf);
3348 g_ptr_array_free(argbuf, TRUE);
3351 lastuid_old = item->lastuid;
3352 *msgnum_list = g_slist_copy(item->uid_list);
3353 nummsgs = g_slist_length(*msgnum_list);
3354 debug_print("Got %d uids from cache\n", g_slist_length(item->uid_list));
3356 for (elem = uidlist; elem != NULL; elem = g_slist_next(elem)) {
3359 msgnum = GPOINTER_TO_INT(elem->data);
3360 if (msgnum > lastuid_old) {
3361 *msgnum_list = g_slist_prepend(*msgnum_list, GINT_TO_POINTER(msgnum));
3362 item->uid_list = g_slist_prepend(item->uid_list, GINT_TO_POINTER(msgnum));
3365 if(msgnum > item->lastuid)
3366 item->lastuid = msgnum;
3369 g_slist_free(uidlist);
3374 gint imap_get_num_list(Folder *folder, FolderItem *_item, GSList **msgnum_list)
3376 IMAPFolderItem *item = (IMAPFolderItem *)_item;
3377 IMAPSession *session;
3378 gint ok, nummsgs = 0, exists, recent, unseen, uid_val, uid_next;
3381 gboolean selected_folder;
3383 g_return_val_if_fail(folder != NULL, -1);
3384 g_return_val_if_fail(item != NULL, -1);
3385 g_return_val_if_fail(item->item.path != NULL, -1);
3386 g_return_val_if_fail(FOLDER_CLASS(folder) == &imap_class, -1);
3387 g_return_val_if_fail(folder->account != NULL, -1);
3389 session = imap_session_get(folder);
3390 g_return_val_if_fail(session != NULL, -1);
3392 selected_folder = (session->mbox != NULL) &&
3393 (!strcmp(session->mbox, item->item.path));
3394 if (selected_folder) {
3395 ok = imap_cmd_noop(session);
3396 if (ok != IMAP_SUCCESS)
3398 exists = session->exists;
3400 ok = imap_status(session, IMAP_FOLDER(folder), item->item.path,
3401 &exists, &recent, &uid_next, &uid_val, &unseen);
3402 if (ok != IMAP_SUCCESS)
3406 /* If old uid_next matches new uid_next we can be sure no message
3407 was added to the folder */
3408 if (( selected_folder && !session->folder_content_changed) ||
3409 (!selected_folder && uid_next == item->uid_next)) {
3410 nummsgs = g_slist_length(item->uid_list);
3412 /* If number of messages is still the same we
3413 know our caches message numbers are still valid,
3414 otherwise if the number of messages has decrease
3415 we discard our cache to start a new scan to find
3416 out which numbers have been removed */
3417 if (exists == nummsgs) {
3418 *msgnum_list = g_slist_copy(item->uid_list);
3420 } else if (exists < nummsgs) {
3421 debug_print("Freeing imap uid cache");
3423 g_slist_free(item->uid_list);
3424 item->uid_list = NULL;
3427 if (!selected_folder)
3428 item->uid_next = uid_next;
3431 *msgnum_list = NULL;
3435 nummsgs = get_list_of_uids(folder, item, &uidlist);
3437 if (nummsgs != exists) {
3438 /* Cache contains more messages then folder, we have cached
3439 an old UID of a message that was removed and new messages
3440 have been added too, otherwise the uid_next check would
3442 debug_print("Freeing imap uid cache");
3444 g_slist_free(item->uid_list);
3445 item->uid_list = NULL;
3447 g_slist_free(*msgnum_list);
3449 nummsgs = get_list_of_uids(folder, item, &uidlist);
3452 *msgnum_list = uidlist;
3454 dir = folder_item_get_path((FolderItem *)item);
3455 debug_print("removing old messages from %s\n", dir);
3456 remove_numbered_files_not_in_list(dir, *msgnum_list);
3462 static MsgInfo *imap_parse_msg(const gchar *file, FolderItem *item)
3467 flags.perm_flags = MSG_NEW|MSG_UNREAD;
3468 flags.tmp_flags = 0;
3470 g_return_val_if_fail(item != NULL, NULL);
3471 g_return_val_if_fail(file != NULL, NULL);
3473 if (item->stype == F_QUEUE) {
3474 MSG_SET_TMP_FLAGS(flags, MSG_QUEUED);
3475 } else if (item->stype == F_DRAFT) {
3476 MSG_SET_TMP_FLAGS(flags, MSG_DRAFT);
3479 msginfo = procheader_parse_file(file, flags, FALSE, FALSE);
3480 if (!msginfo) return NULL;
3482 msginfo->folder = item;
3487 GSList *imap_get_msginfos(Folder *folder, FolderItem *item, GSList *msgnum_list)
3489 IMAPSession *session;
3490 MsgInfoList *ret = NULL;
3493 g_return_val_if_fail(folder != NULL, NULL);
3494 g_return_val_if_fail(item != NULL, NULL);
3495 g_return_val_if_fail(msgnum_list != NULL, NULL);
3497 session = imap_session_get(folder);
3498 g_return_val_if_fail(session != NULL, NULL);
3500 ok = imap_select(session, IMAP_FOLDER(folder), item->path,
3501 NULL, NULL, NULL, NULL);
3502 if (ok != IMAP_SUCCESS)
3505 if (!(item->stype == F_QUEUE || item->stype == F_DRAFT)) {
3506 ret = g_slist_concat(ret,
3507 imap_get_uncached_messages(
3508 session, item, msgnum_list));
3510 MsgNumberList *sorted_list, *elem;
3511 gint startnum, lastnum;
3513 sorted_list = g_slist_sort(g_slist_copy(msgnum_list), g_int_compare);
3515 startnum = lastnum = GPOINTER_TO_INT(sorted_list->data);
3517 for (elem = sorted_list;; elem = g_slist_next(elem)) {
3521 num = GPOINTER_TO_INT(elem->data);
3523 if (num > lastnum + 1 || elem == NULL) {
3525 for (i = startnum; i <= lastnum; ++i) {
3528 file = imap_fetch_msg(folder, item, i);
3530 MsgInfo *msginfo = imap_parse_msg(file, item);
3531 if (msginfo != NULL) {
3532 msginfo->msgnum = i;
3533 ret = g_slist_append(ret, msginfo);
3547 g_slist_free(sorted_list);
3553 MsgInfo *imap_get_msginfo(Folder *folder, FolderItem *item, gint uid)
3555 MsgInfo *msginfo = NULL;
3556 MsgInfoList *msginfolist;
3557 MsgNumberList numlist;
3559 numlist.next = NULL;
3560 numlist.data = GINT_TO_POINTER(uid);
3562 msginfolist = imap_get_msginfos(folder, item, &numlist);
3563 if (msginfolist != NULL) {
3564 msginfo = msginfolist->data;
3565 g_slist_free(msginfolist);
3571 gboolean imap_check_msgnum_validity(Folder *folder, FolderItem *_item)
3573 IMAPSession *session;
3574 IMAPFolderItem *item = (IMAPFolderItem *)_item;
3575 gint ok, exists = 0, recent = 0, unseen = 0;
3576 guint32 uid_next, uid_validity = 0;
3578 g_return_val_if_fail(folder != NULL, FALSE);
3579 g_return_val_if_fail(item != NULL, FALSE);
3580 g_return_val_if_fail(item->item.folder != NULL, FALSE);
3581 g_return_val_if_fail(FOLDER_CLASS(item->item.folder) == &imap_class, FALSE);
3583 session = imap_session_get(folder);
3584 g_return_val_if_fail(session != NULL, FALSE);
3586 ok = imap_status(session, IMAP_FOLDER(folder), item->item.path,
3587 &exists, &recent, &uid_next, &uid_validity, &unseen);
3588 if (ok != IMAP_SUCCESS)
3591 if(item->item.mtime == uid_validity)
3594 debug_print("Freeing imap uid cache\n");
3596 g_slist_free(item->uid_list);
3597 item->uid_list = NULL;
3599 item->item.mtime = uid_validity;
3601 imap_delete_all_cached_messages((FolderItem *)item);
3606 void imap_change_flags(Folder *folder, FolderItem *item, MsgInfo *msginfo, MsgPermFlags newflags)
3608 IMAPSession *session;
3609 IMAPFlags flags_set = 0, flags_unset = 0;
3610 gint ok = IMAP_SUCCESS;
3611 MsgNumberList numlist;
3613 g_return_if_fail(folder != NULL);
3614 g_return_if_fail(folder->klass == &imap_class);
3615 g_return_if_fail(item != NULL);
3616 g_return_if_fail(item->folder == folder);
3617 g_return_if_fail(msginfo != NULL);
3618 g_return_if_fail(msginfo->folder == item);
3620 session = imap_session_get(folder);
3621 if (!session) return;
3623 if ((ok = imap_select(session, IMAP_FOLDER(folder), msginfo->folder->path,
3624 NULL, NULL, NULL, NULL)) != IMAP_SUCCESS)
3627 if (!MSG_IS_MARKED(msginfo->flags) && (newflags & MSG_MARKED))
3628 flags_set |= IMAP_FLAG_FLAGGED;
3629 if ( MSG_IS_MARKED(msginfo->flags) && !(newflags & MSG_MARKED))
3630 flags_unset |= IMAP_FLAG_FLAGGED;
3632 if (!MSG_IS_UNREAD(msginfo->flags) && (newflags & MSG_UNREAD))
3633 flags_unset |= IMAP_FLAG_SEEN;
3634 if ( MSG_IS_UNREAD(msginfo->flags) && !(newflags & MSG_UNREAD))
3635 flags_set |= IMAP_FLAG_SEEN;
3637 if (!MSG_IS_REPLIED(msginfo->flags) && (newflags & MSG_REPLIED))
3638 flags_set |= IMAP_FLAG_ANSWERED;
3639 if ( MSG_IS_REPLIED(msginfo->flags) && !(newflags & MSG_REPLIED))
3640 flags_set |= IMAP_FLAG_ANSWERED;
3642 numlist.next = NULL;
3643 numlist.data = GINT_TO_POINTER(msginfo->msgnum);
3646 ok = imap_set_message_flags(session, &numlist, flags_set, TRUE);
3647 if (ok != IMAP_SUCCESS) return;
3651 ok = imap_set_message_flags(session, &numlist, flags_unset, FALSE);
3652 if (ok != IMAP_SUCCESS) return;
3655 msginfo->flags.perm_flags = newflags;