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 */
442 FolderClass *imap_get_class(void)
447 Folder *imap_folder_new(const gchar *name, const gchar *path)
451 folder = (Folder *)g_new0(IMAPFolder, 1);
452 folder->klass = &imap_class;
453 imap_folder_init(folder, name, path);
458 void imap_folder_destroy(Folder *folder)
462 dir = imap_folder_get_path(folder);
463 if (is_dir_exist(dir))
464 remove_dir_recursive(dir);
467 folder_remote_folder_destroy(REMOTE_FOLDER(folder));
470 static void imap_folder_init(Folder *folder, const gchar *name,
473 folder_remote_folder_init((Folder *)folder, name, path);
476 static FolderItem *imap_folder_item_new(Folder *folder)
478 IMAPFolderItem *item;
480 item = g_new0(IMAPFolderItem, 1);
483 item->uid_list = NULL;
485 return (FolderItem *)item;
488 static void imap_folder_item_destroy(Folder *folder, FolderItem *_item)
490 IMAPFolderItem *item = (IMAPFolderItem *)_item;
492 g_return_if_fail(item != NULL);
493 g_slist_free(item->uid_list);
498 static gboolean imap_reset_uid_lists_func(GNode *node, gpointer data)
500 IMAPFolderItem *item = (IMAPFolderItem *)node->data;
504 g_slist_free(item->uid_list);
505 item->uid_list = NULL;
510 static void imap_reset_uid_lists(Folder *folder)
512 if(folder->node == NULL)
515 /* Destroy all uid lists and rest last uid */
516 g_node_traverse(folder->node, G_IN_ORDER, G_TRAVERSE_ALL, -1, imap_reset_uid_lists_func, NULL);
519 static gint imap_auth(IMAPSession *session, const gchar *user, const gchar *pass,
522 if (type == 0 || type == IMAP_AUTH_LOGIN)
523 return imap_cmd_login(session, user, pass);
525 return imap_cmd_authenticate(session, user, pass, type);
528 static IMAPSession *imap_session_get(Folder *folder)
530 RemoteFolder *rfolder = REMOTE_FOLDER(folder);
531 IMAPSession *session = NULL;
533 g_return_val_if_fail(folder != NULL, NULL);
534 g_return_val_if_fail(FOLDER_CLASS(folder) == &imap_class, NULL);
535 g_return_val_if_fail(folder->account != NULL, NULL);
537 /* Make sure we have a session */
538 if (rfolder->session != NULL) {
539 session = IMAP_SESSION(rfolder->session);
541 imap_reset_uid_lists(folder);
542 session = imap_session_new(folder->account);
547 if (SESSION(session)->sock->state == CONN_DISCONNECTED) {
548 debug_print("IMAP server disconnected\n");
549 session_destroy(SESSION(session));
550 imap_reset_uid_lists(folder);
551 session = imap_session_new(folder->account);
554 /* Make sure session is authenticated */
555 if (!IMAP_SESSION(session)->authenticated)
556 imap_session_authenticate(IMAP_SESSION(session), folder->account);
557 if (!IMAP_SESSION(session)->authenticated) {
558 session_destroy(SESSION(session));
559 rfolder->session = NULL;
563 /* Make sure we have parsed the IMAP namespace */
564 imap_parse_namespace(IMAP_SESSION(session),
565 IMAP_FOLDER(folder));
567 /* I think the point of this code is to avoid sending a
568 * keepalive if we've used the session recently and therefore
569 * think it's still alive. Unfortunately, most of the code
570 * does not yet check for errors on the socket, and so if the
571 * connection drops we don't notice until the timeout expires.
572 * A better solution than sending a NOOP every time would be
573 * for every command to be prepared to retry until it is
574 * successfully sent. -- mbp */
575 if (time(NULL) - session->last_access_time > SESSION_TIMEOUT) {
576 /* verify that the session is still alive */
577 if (imap_cmd_noop(session) != IMAP_SUCCESS) {
578 /* Check if this is the first try to establish a
579 connection, if yes we don't try to reconnect */
580 if (rfolder->session == NULL) {
581 log_warning(_("Connecting %s failed"),
582 folder->account->recv_server);
583 session_destroy(SESSION(session));
586 log_warning(_("IMAP4 connection to %s has been"
587 " disconnected. Reconnecting...\n"),
588 folder->account->recv_server);
589 session_destroy(SESSION(session));
590 /* Clear folders session to make imap_session_get create
591 a new session, because of rfolder->session == NULL
592 it will not try to reconnect again and so avoid an
594 rfolder->session = NULL;
595 session = imap_session_get(folder);
600 rfolder->session = SESSION(session);
602 session->last_access_time = time(NULL);
604 return IMAP_SESSION(session);
607 IMAPSession *imap_session_new(const PrefsAccount *account)
609 IMAPSession *session;
614 /* FIXME: IMAP over SSL only... */
617 port = account->set_imapport ? account->imapport
618 : account->ssl_imap == SSL_TUNNEL ? IMAPS_PORT : IMAP4_PORT;
619 ssl_type = account->ssl_imap;
621 port = account->set_imapport ? account->imapport
625 if (account->set_tunnelcmd) {
626 log_message(_("creating tunneled IMAP4 connection\n"));
628 if ((imap_sock = imap_open_tunnel(account->recv_server,
632 if ((imap_sock = imap_open_tunnel(account->recv_server,
633 account->tunnelcmd)) == NULL)
637 g_return_val_if_fail(account->recv_server != NULL, NULL);
639 log_message(_("creating IMAP4 connection to %s:%d ...\n"),
640 account->recv_server, port);
643 if ((imap_sock = imap_open(account->recv_server, port,
646 if ((imap_sock = imap_open(account->recv_server, port)) == NULL)
651 session = g_new0(IMAPSession, 1);
652 session_init(SESSION(session));
653 SESSION(session)->type = SESSION_IMAP;
654 SESSION(session)->server = g_strdup(account->recv_server);
655 SESSION(session)->sock = imap_sock;
657 SESSION(session)->destroy = imap_session_destroy;
659 session->capability = NULL;
661 session->mbox = NULL;
662 session->authenticated = FALSE;
663 session->cmd_count = 0;
665 /* Only need to log in if the connection was not PREAUTH */
666 if (imap_greeting(session) != IMAP_SUCCESS) {
667 session_destroy(SESSION(session));
672 if (account->ssl_imap == SSL_STARTTLS && imap_has_capability(session, "STARTTLS")) {
675 ok = imap_cmd_starttls(session);
676 if (ok != IMAP_SUCCESS) {
677 log_warning(_("Can't start TLS session.\n"));
678 session_destroy(SESSION(session));
681 if (!ssl_init_socket_with_method(SESSION(session)->sock, SSL_METHOD_TLSv1)) {
682 session_destroy(SESSION(session));
686 imap_free_capabilities(session);
687 session->authenticated = FALSE;
688 session->cmd_count = 1;
690 if (imap_greeting(session) != IMAP_SUCCESS) {
691 session_destroy(SESSION(session));
696 log_message("IMAP connection is %s-authenticated\n",
697 (session->authenticated) ? "pre" : "un");
702 void imap_session_authenticate(IMAPSession *session, const PrefsAccount *account)
706 g_return_if_fail(account->userid != NULL);
708 pass = account->passwd;
711 tmp_pass = input_dialog_query_password(account->recv_server, account->userid);
714 Xstrdup_a(pass, tmp_pass, {g_free(tmp_pass); return;});
718 if (imap_auth(session, account->userid, pass, account->imap_auth_type) != IMAP_SUCCESS) {
719 imap_cmd_logout(session);
723 session->authenticated = TRUE;
726 void imap_session_destroy(Session *session)
728 sock_close(session->sock);
729 session->sock = NULL;
731 g_free(IMAP_SESSION(session)->mbox);
732 imap_free_capabilities(IMAP_SESSION(session));
735 gchar *imap_fetch_msg(Folder *folder, FolderItem *item, gint uid)
737 gchar *path, *filename;
738 IMAPSession *session;
741 g_return_val_if_fail(folder != NULL, NULL);
742 g_return_val_if_fail(item != NULL, NULL);
744 path = folder_item_get_path(item);
745 if (!is_dir_exist(path))
747 filename = g_strconcat(path, G_DIR_SEPARATOR_S, itos(uid), NULL);
750 if (is_file_exist(filename)) {
751 debug_print("message %d has been already cached.\n", uid);
755 session = imap_session_get(folder);
761 ok = imap_select(session, IMAP_FOLDER(folder), item->path,
762 NULL, NULL, NULL, NULL);
763 if (ok != IMAP_SUCCESS) {
764 g_warning("can't select mailbox %s\n", item->path);
769 debug_print("getting message %d...\n", uid);
770 ok = imap_cmd_fetch(session, (guint32)uid, filename);
772 if (ok != IMAP_SUCCESS) {
773 g_warning("can't fetch message %d\n", uid);
781 gint imap_add_msg(Folder *folder, FolderItem *dest, const gchar *file,
782 gboolean remove_source)
785 IMAPSession *session;
788 g_return_val_if_fail(folder != NULL, -1);
789 g_return_val_if_fail(dest != NULL, -1);
790 g_return_val_if_fail(file != NULL, -1);
792 session = imap_session_get(folder);
793 if (!session) return -1;
795 destdir = imap_get_real_path(IMAP_FOLDER(folder), dest->path);
796 ok = imap_cmd_append(session, destdir, file, &newuid);
799 if (ok != IMAP_SUCCESS) {
800 g_warning("can't append message %s\n", file);
805 if (unlink(file) < 0)
806 FILE_OP_ERROR(file, "unlink");
812 static gint imap_do_copy(Folder *folder, FolderItem *dest, MsgInfo *msginfo,
813 gboolean remove_source)
816 IMAPSession *session;
820 g_return_val_if_fail(folder != NULL, -1);
821 g_return_val_if_fail(FOLDER_CLASS(folder) == &imap_class, -1);
822 g_return_val_if_fail(dest != NULL, -1);
823 g_return_val_if_fail(msginfo != NULL, -1);
825 session = imap_session_get(folder);
826 if (!session) return -1;
828 if (msginfo->folder == dest) {
829 g_warning("the src folder is identical to the dest.\n");
833 destdir = imap_get_real_path(IMAP_FOLDER(folder), dest->path);
835 /* ensure source folder selected */
836 ok = imap_select(session, IMAP_FOLDER(folder), msginfo->folder->path,
837 NULL, NULL, NULL, NULL);
838 if (ok != IMAP_SUCCESS)
842 debug_print("Moving message %s%c%d to %s ...\n",
843 msginfo->folder->path, G_DIR_SEPARATOR,
844 msginfo->msgnum, destdir);
846 debug_print("Copying message %s%c%d to %s ...\n",
847 msginfo->folder->path, G_DIR_SEPARATOR,
848 msginfo->msgnum, destdir);
850 ok = imap_cmd_copy(session, msginfo->msgnum, destdir, &newuid);
852 if (ok == IMAP_SUCCESS && remove_source) {
853 MsgNumberList numlist;
856 numlist.data = GINT_TO_POINTER(msginfo->msgnum);
858 imap_set_message_flags(session, &numlist,
859 IMAP_FLAG_DELETED, TRUE);
860 ok = imap_cmd_expunge(session);
865 if (ok == IMAP_SUCCESS)
871 gint imap_copy_msg(Folder *folder, FolderItem *dest, MsgInfo *msginfo)
876 g_return_val_if_fail(folder != NULL, -1);
877 g_return_val_if_fail(dest != NULL, -1);
878 g_return_val_if_fail(msginfo != NULL, -1);
879 g_return_val_if_fail(msginfo->folder != NULL, -1);
881 if (folder == msginfo->folder->folder)
882 return imap_do_copy(folder, dest, msginfo, FALSE);
884 srcfile = procmsg_get_message_file(msginfo);
885 if (!srcfile) return -1;
887 ret = imap_add_msg(folder, dest, srcfile, FALSE);
894 gint imap_remove_msg(Folder *folder, FolderItem *item, gint uid)
897 IMAPSession *session;
899 MsgNumberList numlist;
901 g_return_val_if_fail(folder != NULL, -1);
902 g_return_val_if_fail(FOLDER_CLASS(folder) == &imap_class, -1);
903 g_return_val_if_fail(item != NULL, -1);
905 session = imap_session_get(folder);
906 if (!session) return -1;
908 ok = imap_select(session, IMAP_FOLDER(folder), item->path,
909 NULL, NULL, NULL, NULL);
910 if (ok != IMAP_SUCCESS)
914 numlist.data = GINT_TO_POINTER(uid);
916 ok = imap_set_message_flags
917 (IMAP_SESSION(REMOTE_FOLDER(folder)->session),
918 &numlist, IMAP_FLAG_DELETED, TRUE);
919 if (ok != IMAP_SUCCESS) {
920 log_warning(_("can't set deleted flags: %d\n"), uid);
924 ok = imap_cmd_expunge(session);
925 if (ok != IMAP_SUCCESS) {
926 log_warning(_("can't expunge\n"));
930 dir = folder_item_get_path(item);
931 if (is_dir_exist(dir))
932 remove_numbered_files(dir, uid, uid);
938 gint imap_remove_all_msg(Folder *folder, FolderItem *item)
940 gint exists, recent, unseen;
941 guint32 uid_validity;
943 IMAPSession *session;
946 g_return_val_if_fail(folder != NULL, -1);
947 g_return_val_if_fail(item != NULL, -1);
949 session = imap_session_get(folder);
950 if (!session) return -1;
952 ok = imap_select(session, IMAP_FOLDER(folder), item->path,
953 &exists, &recent, &unseen, &uid_validity);
954 if (ok != IMAP_SUCCESS)
959 imap_gen_send(session,
960 "STORE 1:%d +FLAGS (\\Deleted)", exists);
961 ok = imap_cmd_ok(session, NULL);
962 if (ok != IMAP_SUCCESS) {
963 log_warning(_("can't set deleted flags: 1:%d\n"), exists);
967 ok = imap_cmd_expunge(session);
968 if (ok != IMAP_SUCCESS) {
969 log_warning(_("can't expunge\n"));
973 dir = folder_item_get_path(item);
974 if (is_dir_exist(dir))
975 remove_all_numbered_files(dir);
981 gboolean imap_is_msg_changed(Folder *folder, FolderItem *item, MsgInfo *msginfo)
983 /* TODO: properly implement this method */
987 void imap_scan_tree(Folder *folder)
990 IMAPSession *session;
991 gchar *root_folder = NULL;
993 g_return_if_fail(folder != NULL);
994 g_return_if_fail(folder->account != NULL);
996 session = imap_session_get(folder);
999 folder_tree_destroy(folder);
1000 item = folder_item_new(folder, folder->name, NULL);
1001 item->folder = folder;
1002 folder->node = g_node_new(item);
1007 if (folder->account->imap_dir && *folder->account->imap_dir) {
1008 Xstrdup_a(root_folder, folder->account->imap_dir, return);
1009 strtailchomp(root_folder, '/');
1010 debug_print("IMAP root directory: %s\n", root_folder);
1013 item = folder_item_new(folder, folder->name, root_folder);
1014 item->folder = folder;
1015 item->no_select = TRUE;
1016 folder->node = g_node_new(item);
1018 imap_scan_tree_recursive(session, item);
1020 imap_create_missing_folders(folder);
1023 static gint imap_scan_tree_recursive(IMAPSession *session, FolderItem *item)
1026 IMAPFolder *imapfolder;
1027 FolderItem *new_item;
1028 GSList *item_list, *cur;
1030 gchar *wildcard_path;
1034 g_return_val_if_fail(item != NULL, -1);
1035 g_return_val_if_fail(item->folder != NULL, -1);
1036 g_return_val_if_fail(item->no_sub == FALSE, -1);
1038 folder = FOLDER(item->folder);
1039 imapfolder = IMAP_FOLDER(folder);
1041 separator = imap_get_path_separator(imapfolder, item->path);
1043 if (item->folder->ui_func)
1044 item->folder->ui_func(folder, item, folder->ui_func_data);
1047 wildcard[0] = separator;
1050 real_path = imap_get_real_path(imapfolder, item->path);
1054 real_path = g_strdup("");
1057 Xstrcat_a(wildcard_path, real_path, wildcard,
1058 {g_free(real_path); return IMAP_ERROR;});
1059 QUOTE_IF_REQUIRED(wildcard_path, wildcard_path);
1061 imap_gen_send(session, "LIST \"\" %s",
1064 strtailchomp(real_path, separator);
1065 item_list = imap_parse_list(imapfolder, session, real_path, NULL);
1068 for (cur = item_list; cur != NULL; cur = cur->next) {
1069 new_item = cur->data;
1070 if (!strcmp(new_item->path, "INBOX")) {
1071 if (!folder->inbox) {
1072 new_item->stype = F_INBOX;
1073 item->folder->inbox = new_item;
1075 folder_item_destroy(new_item);
1078 } else if (!item->parent || item->stype == F_INBOX) {
1081 base = g_basename(new_item->path);
1083 if (!folder->outbox && !strcasecmp(base, "Sent")) {
1084 new_item->stype = F_OUTBOX;
1085 folder->outbox = new_item;
1086 } else if (!folder->draft && !strcasecmp(base, "Drafts")) {
1087 new_item->stype = F_DRAFT;
1088 folder->draft = new_item;
1089 } else if (!folder->queue && !strcasecmp(base, "Queue")) {
1090 new_item->stype = F_QUEUE;
1091 folder->queue = new_item;
1092 } else if (!folder->trash && !strcasecmp(base, "Trash")) {
1093 new_item->stype = F_TRASH;
1094 folder->trash = new_item;
1097 folder_item_append(item, new_item);
1098 if (new_item->no_sub == FALSE)
1099 imap_scan_tree_recursive(session, new_item);
1102 return IMAP_SUCCESS;
1105 static GSList *imap_parse_list(IMAPFolder *folder, IMAPSession *session,
1106 const gchar *real_path, gchar *separator)
1108 gchar buf[IMAPBUFSIZE];
1110 gchar separator_str[16];
1113 gchar *loc_name, *loc_path;
1114 GSList *item_list = NULL;
1116 FolderItem *new_item;
1118 debug_print("getting list of %s ...\n",
1119 *real_path ? real_path : "\"\"");
1121 str = g_string_new(NULL);
1124 if (sock_gets(SESSION(session)->sock, buf, sizeof(buf)) <= 0) {
1125 log_warning(_("error occurred while getting LIST.\n"));
1129 if (buf[0] != '*' || buf[1] != ' ') {
1130 log_print("IMAP4< %s\n", buf);
1133 debug_print("IMAP4< %s\n", buf);
1135 g_string_assign(str, buf);
1137 if (strncmp(p, "LIST ", 5) != 0) continue;
1140 if (*p != '(') continue;
1142 p = strchr_cpy(p, ')', flags, sizeof(flags));
1144 while (*p == ' ') p++;
1146 p = strchr_cpy(p, ' ', separator_str, sizeof(separator_str));
1148 extract_quote(separator_str, '"');
1149 if (!strcmp(separator_str, "NIL"))
1150 separator_str[0] = '\0';
1152 *separator = separator_str[0];
1155 while (*p == ' ') p++;
1156 if (*p == '{' || *p == '"')
1157 p = imap_parse_atom(SESSION(session)->sock, p,
1158 buf, sizeof(buf), str);
1160 strncpy2(buf, p, sizeof(buf));
1161 strtailchomp(buf, separator_str[0]);
1162 if (buf[0] == '\0') continue;
1163 if (!strcmp(buf, real_path)) continue;
1165 if (separator_str[0] != '\0')
1166 subst_char(buf, separator_str[0], '/');
1167 name = g_basename(buf);
1168 if (name[0] == '.') continue;
1170 loc_name = imap_modified_utf7_to_locale(name);
1171 loc_path = imap_modified_utf7_to_locale(buf);
1172 new_item = folder_item_new(FOLDER(folder), loc_name, loc_path);
1173 if (strcasestr(flags, "\\Noinferiors") != NULL)
1174 new_item->no_sub = TRUE;
1175 if (strcmp(buf, "INBOX") != 0 &&
1176 strcasestr(flags, "\\Noselect") != NULL)
1177 new_item->no_select = TRUE;
1179 item_list = g_slist_append(item_list, new_item);
1181 debug_print("folder %s has been added.\n", loc_path);
1186 g_string_free(str, TRUE);
1191 gint imap_create_tree(Folder *folder)
1193 g_return_val_if_fail(folder != NULL, -1);
1194 g_return_val_if_fail(folder->node != NULL, -1);
1195 g_return_val_if_fail(folder->node->data != NULL, -1);
1196 g_return_val_if_fail(folder->account != NULL, -1);
1198 imap_scan_tree(folder);
1199 imap_create_missing_folders(folder);
1204 static void imap_create_missing_folders(Folder *folder)
1206 g_return_if_fail(folder != NULL);
1209 folder->inbox = imap_create_special_folder
1210 (folder, F_INBOX, "INBOX");
1212 if (!folder->outbox)
1213 folder->outbox = imap_create_special_folder
1214 (folder, F_OUTBOX, "Sent");
1216 folder->draft = imap_create_special_folder
1217 (folder, F_DRAFT, "Drafts");
1219 folder->queue = imap_create_special_folder
1220 (folder, F_QUEUE, "Queue");
1223 folder->trash = imap_create_special_folder
1224 (folder, F_TRASH, "Trash");
1227 static FolderItem *imap_create_special_folder(Folder *folder,
1228 SpecialFolderItemType stype,
1232 FolderItem *new_item;
1234 g_return_val_if_fail(folder != NULL, NULL);
1235 g_return_val_if_fail(folder->node != NULL, NULL);
1236 g_return_val_if_fail(folder->node->data != NULL, NULL);
1237 g_return_val_if_fail(folder->account != NULL, NULL);
1238 g_return_val_if_fail(name != NULL, NULL);
1240 item = FOLDER_ITEM(folder->node->data);
1241 new_item = imap_create_folder(folder, item, name);
1244 g_warning("Can't create '%s'\n", name);
1245 if (!folder->inbox) return NULL;
1247 new_item = imap_create_folder(folder, folder->inbox, name);
1249 g_warning("Can't create '%s' under INBOX\n", name);
1251 new_item->stype = stype;
1253 new_item->stype = stype;
1258 static gchar *imap_folder_get_path(Folder *folder)
1262 g_return_val_if_fail(folder != NULL, NULL);
1263 g_return_val_if_fail(folder->account != NULL, NULL);
1265 folder_path = g_strconcat(get_imap_cache_dir(),
1267 folder->account->recv_server,
1269 folder->account->userid,
1275 static gchar *imap_item_get_path(Folder *folder, FolderItem *item)
1277 gchar *folder_path, *path;
1279 g_return_val_if_fail(folder != NULL, NULL);
1280 g_return_val_if_fail(item != NULL, NULL);
1281 folder_path = imap_folder_get_path(folder);
1283 g_return_val_if_fail(folder_path != NULL, NULL);
1284 if (folder_path[0] == G_DIR_SEPARATOR) {
1286 path = g_strconcat(folder_path, G_DIR_SEPARATOR_S,
1289 path = g_strdup(folder_path);
1292 path = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S,
1293 folder_path, G_DIR_SEPARATOR_S,
1296 path = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S,
1299 g_free(folder_path);
1304 FolderItem *imap_create_folder(Folder *folder, FolderItem *parent,
1307 gchar *dirpath, *imap_path;
1308 IMAPSession *session;
1309 FolderItem *new_item;
1315 g_return_val_if_fail(folder != NULL, NULL);
1316 g_return_val_if_fail(folder->account != NULL, NULL);
1317 g_return_val_if_fail(parent != NULL, NULL);
1318 g_return_val_if_fail(name != NULL, NULL);
1320 session = imap_session_get(folder);
1321 if (!session) return NULL;
1323 if (!parent->parent && strcmp(name, "INBOX") == 0)
1324 dirpath = g_strdup(name);
1325 else if (parent->path)
1326 dirpath = g_strconcat(parent->path, "/", name, NULL);
1327 else if ((p = strchr(name, '/')) != NULL && *(p + 1) != '\0')
1328 dirpath = g_strdup(name);
1329 else if (folder->account->imap_dir && *folder->account->imap_dir) {
1332 Xstrdup_a(imap_dir, folder->account->imap_dir, return NULL);
1333 strtailchomp(imap_dir, '/');
1334 dirpath = g_strconcat(imap_dir, "/", name, NULL);
1336 dirpath = g_strdup(name);
1338 /* keep trailing directory separator to create a folder that contains
1340 imap_path = imap_locale_to_modified_utf7(dirpath);
1341 strtailchomp(dirpath, '/');
1342 Xstrdup_a(new_name, name, {g_free(dirpath); return NULL;});
1343 strtailchomp(new_name, '/');
1344 separator = imap_get_path_separator(IMAP_FOLDER(folder), imap_path);
1345 imap_path_separator_subst(imap_path, separator);
1346 subst_char(new_name, '/', separator);
1348 if (strcmp(name, "INBOX") != 0) {
1351 gboolean exist = FALSE;
1353 argbuf = g_ptr_array_new();
1354 ok = imap_cmd_list(session, NULL, imap_path,
1356 if (ok != IMAP_SUCCESS) {
1357 log_warning(_("can't create mailbox: LIST failed\n"));
1360 ptr_array_free_strings(argbuf);
1361 g_ptr_array_free(argbuf, TRUE);
1365 for (i = 0; i < argbuf->len; i++) {
1367 str = g_ptr_array_index(argbuf, i);
1368 if (!strncmp(str, "LIST ", 5)) {
1373 ptr_array_free_strings(argbuf);
1374 g_ptr_array_free(argbuf, TRUE);
1377 ok = imap_cmd_create(session, imap_path);
1378 if (ok != IMAP_SUCCESS) {
1379 log_warning(_("can't create mailbox\n"));
1387 new_item = folder_item_new(folder, new_name, dirpath);
1388 folder_item_append(parent, new_item);
1392 dirpath = folder_item_get_path(new_item);
1393 if (!is_dir_exist(dirpath))
1394 make_dir_hier(dirpath);
1400 gint imap_rename_folder(Folder *folder, FolderItem *item, const gchar *name)
1404 gchar *real_oldpath;
1405 gchar *real_newpath;
1408 gchar *old_cache_dir;
1409 gchar *new_cache_dir;
1410 IMAPSession *session;
1413 gint exists, recent, unseen;
1414 guint32 uid_validity;
1416 g_return_val_if_fail(folder != NULL, -1);
1417 g_return_val_if_fail(item != NULL, -1);
1418 g_return_val_if_fail(item->path != NULL, -1);
1419 g_return_val_if_fail(name != NULL, -1);
1421 session = imap_session_get(folder);
1422 if (!session) return -1;
1424 real_oldpath = imap_get_real_path(IMAP_FOLDER(folder), item->path);
1426 g_free(session->mbox);
1427 session->mbox = NULL;
1428 ok = imap_cmd_examine(session, "INBOX",
1429 &exists, &recent, &unseen, &uid_validity);
1430 if (ok != IMAP_SUCCESS) {
1431 g_free(real_oldpath);
1435 separator = imap_get_path_separator(IMAP_FOLDER(folder), item->path);
1436 if (strchr(item->path, G_DIR_SEPARATOR)) {
1437 dirpath = g_dirname(item->path);
1438 newpath = g_strconcat(dirpath, G_DIR_SEPARATOR_S, name, NULL);
1441 newpath = g_strdup(name);
1443 real_newpath = imap_locale_to_modified_utf7(newpath);
1444 imap_path_separator_subst(real_newpath, separator);
1446 ok = imap_cmd_rename(session, real_oldpath, real_newpath);
1447 if (ok != IMAP_SUCCESS) {
1448 log_warning(_("can't rename mailbox: %s to %s\n"),
1449 real_oldpath, real_newpath);
1450 g_free(real_oldpath);
1452 g_free(real_newpath);
1457 item->name = g_strdup(name);
1459 old_cache_dir = folder_item_get_path(item);
1461 node = g_node_find(item->folder->node, G_PRE_ORDER, G_TRAVERSE_ALL,
1463 paths[0] = g_strdup(item->path);
1465 g_node_traverse(node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
1466 imap_rename_folder_func, paths);
1468 if (is_dir_exist(old_cache_dir)) {
1469 new_cache_dir = folder_item_get_path(item);
1470 if (rename(old_cache_dir, new_cache_dir) < 0) {
1471 FILE_OP_ERROR(old_cache_dir, "rename");
1473 g_free(new_cache_dir);
1476 g_free(old_cache_dir);
1479 g_free(real_oldpath);
1480 g_free(real_newpath);
1485 gint imap_remove_folder(Folder *folder, FolderItem *item)
1488 IMAPSession *session;
1491 gint exists, recent, unseen;
1492 guint32 uid_validity;
1494 g_return_val_if_fail(folder != NULL, -1);
1495 g_return_val_if_fail(item != NULL, -1);
1496 g_return_val_if_fail(item->path != NULL, -1);
1498 session = imap_session_get(folder);
1499 if (!session) return -1;
1501 path = imap_get_real_path(IMAP_FOLDER(folder), item->path);
1503 ok = imap_cmd_examine(session, "INBOX",
1504 &exists, &recent, &unseen, &uid_validity);
1505 if (ok != IMAP_SUCCESS) {
1510 ok = imap_cmd_delete(session, path);
1511 if (ok != IMAP_SUCCESS) {
1512 log_warning(_("can't delete mailbox\n"));
1518 cache_dir = folder_item_get_path(item);
1519 if (is_dir_exist(cache_dir) && remove_dir_recursive(cache_dir) < 0)
1520 g_warning("can't remove directory '%s'\n", cache_dir);
1522 folder_item_remove(item);
1527 static GSList *imap_get_uncached_messages(IMAPSession *session,
1529 MsgNumberList *numlist)
1532 GSList *newlist = NULL;
1533 GSList *llast = NULL;
1538 g_return_val_if_fail(session != NULL, NULL);
1539 g_return_val_if_fail(item != NULL, NULL);
1540 g_return_val_if_fail(item->folder != NULL, NULL);
1541 g_return_val_if_fail(FOLDER_CLASS(item->folder) == &imap_class, NULL);
1543 imapset = numberlist_to_imapset(numlist);
1544 while (imapset != NULL) {
1545 if (imap_cmd_envelope(session, imapset)
1547 log_warning(_("can't get envelope\n"));
1551 str = g_string_new(NULL);
1554 if ((tmp = sock_getline(SESSION(session)->sock)) == NULL) {
1555 log_warning(_("error occurred while getting envelope.\n"));
1556 g_string_free(str, TRUE);
1560 if (tmp[0] != '*' || tmp[1] != ' ') {
1561 log_print("IMAP4< %s\n", tmp);
1565 if (strstr(tmp, "FETCH") == NULL) {
1566 log_print("IMAP4< %s\n", tmp);
1570 log_print("IMAP4< %s\n", tmp);
1571 g_string_assign(str, tmp);
1574 msginfo = imap_parse_envelope
1575 (SESSION(session)->sock, item, str);
1577 log_warning(_("can't parse envelope: %s\n"), str->str);
1580 if (item->stype == F_QUEUE) {
1581 MSG_SET_TMP_FLAGS(msginfo->flags, MSG_QUEUED);
1582 } else if (item->stype == F_DRAFT) {
1583 MSG_SET_TMP_FLAGS(msginfo->flags, MSG_DRAFT);
1586 msginfo->folder = item;
1589 llast = newlist = g_slist_append(newlist, msginfo);
1591 llast = g_slist_append(llast, msginfo);
1592 llast = llast->next;
1596 g_string_free(str, TRUE);
1597 imapset = numberlist_to_imapset(NULL);
1603 static void imap_delete_all_cached_messages(FolderItem *item)
1607 g_return_if_fail(item != NULL);
1608 g_return_if_fail(item->folder != NULL);
1609 g_return_if_fail(FOLDER_CLASS(item->folder) == &imap_class);
1611 debug_print("Deleting all cached messages...\n");
1613 dir = folder_item_get_path(item);
1614 if (is_dir_exist(dir))
1615 remove_all_numbered_files(dir);
1618 debug_print("done.\n");
1622 static SockInfo *imap_open_tunnel(const gchar *server,
1623 const gchar *tunnelcmd,
1626 static SockInfo *imap_open_tunnel(const gchar *server,
1627 const gchar *tunnelcmd)
1632 if ((sock = sock_connect_cmd(server, tunnelcmd)) == NULL) {
1633 log_warning(_("Can't establish IMAP4 session with: %s\n"),
1638 return imap_init_sock(sock, ssl_type);
1640 return imap_init_sock(sock);
1646 static SockInfo *imap_open(const gchar *server, gushort port,
1649 static SockInfo *imap_open(const gchar *server, gushort port)
1654 if ((sock = sock_connect(server, port)) == NULL) {
1655 log_warning(_("Can't connect to IMAP4 server: %s:%d\n"),
1661 if (ssl_type == SSL_TUNNEL && !ssl_init_socket(sock)) {
1662 log_warning(_("Can't establish IMAP4 session with: %s:%d\n"),
1672 static SockInfo *imap_init_sock(SockInfo *sock, SSLType ssl_type)
1674 static SockInfo *imap_init_sock(SockInfo *sock)
1681 static GList *imap_parse_namespace_str(gchar *str)
1686 IMAPNameSpace *namespace;
1687 GList *ns_list = NULL;
1689 while (*p != '\0') {
1690 /* parse ("#foo" "/") */
1692 while (*p && *p != '(') p++;
1693 if (*p == '\0') break;
1696 while (*p && *p != '"') p++;
1697 if (*p == '\0') break;
1701 while (*p && *p != '"') p++;
1702 if (*p == '\0') break;
1706 while (*p && isspace(*p)) p++;
1707 if (*p == '\0') break;
1708 if (strncmp(p, "NIL", 3) == 0)
1710 else if (*p == '"') {
1713 while (*p && *p != '"') p++;
1714 if (*p == '\0') break;
1719 while (*p && *p != ')') p++;
1720 if (*p == '\0') break;
1723 namespace = g_new(IMAPNameSpace, 1);
1724 namespace->name = g_strdup(name);
1725 namespace->separator = separator ? separator[0] : '\0';
1726 ns_list = g_list_append(ns_list, namespace);
1732 static void imap_parse_namespace(IMAPSession *session, IMAPFolder *folder)
1737 g_return_if_fail(session != NULL);
1738 g_return_if_fail(folder != NULL);
1740 if (folder->ns_personal != NULL ||
1741 folder->ns_others != NULL ||
1742 folder->ns_shared != NULL)
1745 if (!imap_has_capability(session, "NAMESPACE")) {
1746 imap_get_namespace_by_list(session, folder);
1750 if (imap_cmd_namespace(session, &ns_str)
1752 log_warning(_("can't get namespace\n"));
1756 str_array = strsplit_parenthesis(ns_str, '(', ')', 3);
1757 if (str_array == NULL) {
1759 imap_get_namespace_by_list(session, folder);
1763 folder->ns_personal = imap_parse_namespace_str(str_array[0]);
1764 if (str_array[0] && str_array[1])
1765 folder->ns_others = imap_parse_namespace_str(str_array[1]);
1766 if (str_array[0] && str_array[1] && str_array[2])
1767 folder->ns_shared = imap_parse_namespace_str(str_array[2]);
1768 g_strfreev(str_array);
1772 static void imap_get_namespace_by_list(IMAPSession *session, IMAPFolder *folder)
1774 GSList *item_list, *cur;
1775 gchar separator = '\0';
1776 IMAPNameSpace *namespace;
1778 g_return_if_fail(session != NULL);
1779 g_return_if_fail(folder != NULL);
1781 if (folder->ns_personal != NULL ||
1782 folder->ns_others != NULL ||
1783 folder->ns_shared != NULL)
1786 imap_gen_send(session, "LIST \"\" \"\"");
1787 item_list = imap_parse_list(folder, session, "", &separator);
1788 for (cur = item_list; cur != NULL; cur = cur->next)
1789 folder_item_destroy(FOLDER_ITEM(cur->data));
1790 g_slist_free(item_list);
1792 namespace = g_new(IMAPNameSpace, 1);
1793 namespace->name = g_strdup("");
1794 namespace->separator = separator;
1795 folder->ns_personal = g_list_append(NULL, namespace);
1798 static IMAPNameSpace *imap_find_namespace_from_list(GList *ns_list,
1801 IMAPNameSpace *namespace = NULL;
1802 gchar *tmp_path, *name;
1804 if (!path) path = "";
1806 Xstrcat_a(tmp_path, path, "/", return NULL);
1808 for (; ns_list != NULL; ns_list = ns_list->next) {
1809 IMAPNameSpace *tmp_ns = ns_list->data;
1811 Xstrdup_a(name, tmp_ns->name, return namespace);
1812 if (tmp_ns->separator && tmp_ns->separator != '/')
1813 subst_char(name, tmp_ns->separator, '/');
1814 if (strncmp(tmp_path, name, strlen(name)) == 0)
1821 static IMAPNameSpace *imap_find_namespace(IMAPFolder *folder,
1824 IMAPNameSpace *namespace;
1826 g_return_val_if_fail(folder != NULL, NULL);
1828 namespace = imap_find_namespace_from_list(folder->ns_personal, path);
1829 if (namespace) return namespace;
1830 namespace = imap_find_namespace_from_list(folder->ns_others, path);
1831 if (namespace) return namespace;
1832 namespace = imap_find_namespace_from_list(folder->ns_shared, path);
1833 if (namespace) return namespace;
1838 static gchar imap_get_path_separator(IMAPFolder *folder, const gchar *path)
1840 IMAPNameSpace *namespace;
1841 gchar separator = '/';
1843 namespace = imap_find_namespace(folder, path);
1844 if (namespace && namespace->separator)
1845 separator = namespace->separator;
1850 static gchar *imap_get_real_path(IMAPFolder *folder, const gchar *path)
1855 g_return_val_if_fail(folder != NULL, NULL);
1856 g_return_val_if_fail(path != NULL, NULL);
1858 real_path = imap_locale_to_modified_utf7(path);
1859 separator = imap_get_path_separator(folder, path);
1860 imap_path_separator_subst(real_path, separator);
1865 static gchar *imap_parse_atom(SockInfo *sock, gchar *src,
1866 gchar *dest, gint dest_len, GString *str)
1868 gchar *cur_pos = src;
1871 g_return_val_if_fail(str != NULL, cur_pos);
1873 /* read the next line if the current response buffer is empty */
1874 while (isspace(*cur_pos)) cur_pos++;
1875 while (*cur_pos == '\0') {
1876 if ((nextline = sock_getline(sock)) == NULL)
1878 g_string_assign(str, nextline);
1880 strretchomp(nextline);
1881 /* log_print("IMAP4< %s\n", nextline); */
1882 debug_print("IMAP4< %s\n", nextline);
1885 while (isspace(*cur_pos)) cur_pos++;
1888 if (!strncmp(cur_pos, "NIL", 3)) {
1891 } else if (*cur_pos == '\"') {
1894 p = get_quoted(cur_pos, '\"', dest, dest_len);
1895 cur_pos = p ? p : cur_pos + 2;
1896 } else if (*cur_pos == '{') {
1901 cur_pos = strchr_cpy(cur_pos + 1, '}', buf, sizeof(buf));
1903 g_return_val_if_fail(len > 0, cur_pos);
1905 g_string_truncate(str, 0);
1909 if ((nextline = sock_getline(sock)) == NULL)
1911 line_len += strlen(nextline);
1912 g_string_append(str, nextline);
1914 strretchomp(nextline);
1915 /* log_print("IMAP4< %s\n", nextline); */
1916 debug_print("IMAP4< %s\n", nextline);
1918 } while (line_len < len);
1920 memcpy(dest, cur_pos, MIN(len, dest_len - 1));
1921 dest[MIN(len, dest_len - 1)] = '\0';
1928 static gchar *imap_get_header(SockInfo *sock, gchar *cur_pos, gchar **headers,
1938 g_return_val_if_fail(str != NULL, cur_pos);
1940 while (isspace(*cur_pos)) cur_pos++;
1942 g_return_val_if_fail(*cur_pos == '{', cur_pos);
1944 cur_pos = strchr_cpy(cur_pos + 1, '}', buf, sizeof(buf));
1946 g_return_val_if_fail(len > 0, cur_pos);
1948 g_string_truncate(str, 0);
1952 if ((nextline = sock_getline(sock)) == NULL)
1954 block_len += strlen(nextline);
1955 g_string_append(str, nextline);
1957 strretchomp(nextline);
1958 /* debug_print("IMAP4< %s\n", nextline); */
1960 } while (block_len < len);
1962 debug_print("IMAP4< [contents of BODY.PEEK[HEADER.FIELDS (...)]]\n");
1964 *headers = g_strndup(cur_pos, len);
1967 while (isspace(*cur_pos)) cur_pos++;
1968 while (*cur_pos == '\0') {
1969 if ((nextline = sock_getline(sock)) == NULL)
1971 g_string_assign(str, nextline);
1973 strretchomp(nextline);
1974 debug_print("IMAP4< %s\n", nextline);
1977 while (isspace(*cur_pos)) cur_pos++;
1983 static MsgFlags imap_parse_flags(const gchar *flag_str)
1985 const gchar *p = flag_str;
1986 MsgFlags flags = {0, 0};
1988 flags.perm_flags = MSG_UNREAD;
1990 while ((p = strchr(p, '\\')) != NULL) {
1993 if (g_strncasecmp(p, "Recent", 6) == 0 && MSG_IS_UNREAD(flags)) {
1994 MSG_SET_PERM_FLAGS(flags, MSG_NEW);
1995 } else if (g_strncasecmp(p, "Seen", 4) == 0) {
1996 MSG_UNSET_PERM_FLAGS(flags, MSG_NEW|MSG_UNREAD);
1997 } else if (g_strncasecmp(p, "Deleted", 7) == 0) {
1998 MSG_SET_PERM_FLAGS(flags, MSG_DELETED);
1999 } else if (g_strncasecmp(p, "Flagged", 7) == 0) {
2000 MSG_SET_PERM_FLAGS(flags, MSG_MARKED);
2001 } else if (g_strncasecmp(p, "Answered", 8) == 0) {
2002 MSG_SET_PERM_FLAGS(flags, MSG_REPLIED);
2009 static MsgInfo *imap_parse_envelope(SockInfo *sock, FolderItem *item,
2012 gchar buf[IMAPBUFSIZE];
2013 MsgInfo *msginfo = NULL;
2018 MsgFlags flags = {0, 0}, imap_flags = {0, 0};
2020 g_return_val_if_fail(line_str != NULL, NULL);
2021 g_return_val_if_fail(line_str->str[0] == '*' &&
2022 line_str->str[1] == ' ', NULL);
2024 MSG_SET_TMP_FLAGS(flags, MSG_IMAP);
2025 if (item->stype == F_QUEUE) {
2026 MSG_SET_TMP_FLAGS(flags, MSG_QUEUED);
2027 } else if (item->stype == F_DRAFT) {
2028 MSG_SET_TMP_FLAGS(flags, MSG_DRAFT);
2031 cur_pos = line_str->str + 2;
2033 #define PARSE_ONE_ELEMENT(ch) \
2035 cur_pos = strchr_cpy(cur_pos, ch, buf, sizeof(buf)); \
2036 if (cur_pos == NULL) { \
2037 g_warning("cur_pos == NULL\n"); \
2038 procmsg_msginfo_free(msginfo); \
2043 PARSE_ONE_ELEMENT(' ');
2046 PARSE_ONE_ELEMENT(' ');
2047 g_return_val_if_fail(!strcmp(buf, "FETCH"), NULL);
2049 g_return_val_if_fail(*cur_pos == '(', NULL);
2052 while (*cur_pos != '\0' && *cur_pos != ')') {
2053 while (*cur_pos == ' ') cur_pos++;
2055 if (!strncmp(cur_pos, "UID ", 4)) {
2057 uid = strtoul(cur_pos, &cur_pos, 10);
2058 } else if (!strncmp(cur_pos, "FLAGS ", 6)) {
2060 if (*cur_pos != '(') {
2061 g_warning("*cur_pos != '('\n");
2062 procmsg_msginfo_free(msginfo);
2066 PARSE_ONE_ELEMENT(')');
2067 imap_flags = imap_parse_flags(buf);
2068 } else if (!strncmp(cur_pos, "RFC822.SIZE ", 12)) {
2070 size = strtol(cur_pos, &cur_pos, 10);
2071 } else if (!strncmp(cur_pos, "BODY[HEADER.FIELDS ", 19)) {
2075 if (*cur_pos != '(') {
2076 g_warning("*cur_pos != '('\n");
2077 procmsg_msginfo_free(msginfo);
2081 PARSE_ONE_ELEMENT(')');
2082 if (*cur_pos != ']') {
2083 g_warning("*cur_pos != ']'\n");
2084 procmsg_msginfo_free(msginfo);
2089 cur_pos = imap_get_header(sock, cur_pos, &headers,
2091 msginfo = procheader_parse_str(headers, flags, FALSE, FALSE);
2094 g_warning("invalid FETCH response: %s\n", cur_pos);
2100 msginfo->msgnum = uid;
2101 msginfo->size = size;
2102 msginfo->flags.tmp_flags |= imap_flags.tmp_flags;
2103 msginfo->flags.perm_flags = imap_flags.perm_flags;
2109 static gint imap_set_message_flags(IMAPSession *session,
2110 MsgNumberList *numlist,
2118 buf = g_string_new(is_set ? "+FLAGS (" : "-FLAGS (");
2120 if (IMAP_IS_SEEN(flags)) g_string_append(buf, "\\Seen ");
2121 if (IMAP_IS_ANSWERED(flags)) g_string_append(buf, "\\Answered ");
2122 if (IMAP_IS_FLAGGED(flags)) g_string_append(buf, "\\Flagged ");
2123 if (IMAP_IS_DELETED(flags)) g_string_append(buf, "\\Deleted ");
2124 if (IMAP_IS_DRAFT(flags)) g_string_append(buf, "\\Draft");
2126 if (buf->str[buf->len - 1] == ' ')
2127 g_string_truncate(buf, buf->len - 1);
2129 g_string_append_c(buf, ')');
2131 imapset = numberlist_to_imapset(numlist);
2132 while (imapset != NULL) {
2133 ok = imap_cmd_store(session, imapset,
2135 imapset = numberlist_to_imapset(NULL);
2137 g_string_free(buf, TRUE);
2142 static gint imap_select(IMAPSession *session, IMAPFolder *folder,
2144 gint *exists, gint *recent, gint *unseen,
2145 guint32 *uid_validity)
2149 gint exists_, recent_, unseen_, uid_validity_;
2151 if (!exists || !recent || !unseen || !uid_validity) {
2152 if (session->mbox && strcmp(session->mbox, path) == 0)
2153 return IMAP_SUCCESS;
2157 uid_validity = &uid_validity_;
2160 g_free(session->mbox);
2161 session->mbox = NULL;
2163 real_path = imap_get_real_path(folder, path);
2164 ok = imap_cmd_select(session, real_path,
2165 exists, recent, unseen, uid_validity);
2166 if (ok != IMAP_SUCCESS)
2167 log_warning(_("can't select folder: %s\n"), real_path);
2169 session->mbox = g_strdup(path);
2170 session->folder_content_changed = FALSE;
2177 #define THROW(err) { ok = err; goto catch; }
2179 static gint imap_status(IMAPSession *session, IMAPFolder *folder,
2181 gint *messages, gint *recent,
2182 guint32 *uid_next, guint32 *uid_validity,
2191 *messages = *recent = *uid_next = *uid_validity = *unseen = 0;
2193 argbuf = g_ptr_array_new();
2195 real_path = imap_get_real_path(folder, path);
2196 QUOTE_IF_REQUIRED(real_path_, real_path);
2197 imap_gen_send(session, "STATUS %s "
2198 "(MESSAGES RECENT UIDNEXT UIDVALIDITY UNSEEN)",
2201 ok = imap_cmd_ok(session, argbuf);
2202 if (ok != IMAP_SUCCESS) THROW(ok);
2204 str = search_array_str(argbuf, "STATUS");
2205 if (!str) THROW(IMAP_ERROR);
2207 str = strchr(str, '(');
2208 if (!str) THROW(IMAP_ERROR);
2210 while (*str != '\0' && *str != ')') {
2211 while (*str == ' ') str++;
2213 if (!strncmp(str, "MESSAGES ", 9)) {
2215 *messages = strtol(str, &str, 10);
2216 } else if (!strncmp(str, "RECENT ", 7)) {
2218 *recent = strtol(str, &str, 10);
2219 } else if (!strncmp(str, "UIDNEXT ", 8)) {
2221 *uid_next = strtoul(str, &str, 10);
2222 } else if (!strncmp(str, "UIDVALIDITY ", 12)) {
2224 *uid_validity = strtoul(str, &str, 10);
2225 } else if (!strncmp(str, "UNSEEN ", 7)) {
2227 *unseen = strtol(str, &str, 10);
2229 g_warning("invalid STATUS response: %s\n", str);
2236 ptr_array_free_strings(argbuf);
2237 g_ptr_array_free(argbuf, TRUE);
2245 /* low-level IMAP4rev1 commands */
2247 static gint imap_cmd_authenticate(IMAPSession *session, const gchar *user,
2248 const gchar *pass, IMAPAuthType type)
2255 gchar hexdigest[33];
2259 auth_type = "CRAM-MD5";
2261 imap_gen_send(session, "AUTHENTICATE %s", auth_type);
2262 ok = imap_gen_recv(session, &buf);
2263 if (ok != IMAP_SUCCESS || buf[0] != '+' || buf[1] != ' ') {
2268 challenge = g_malloc(strlen(buf + 2) + 1);
2269 challenge_len = base64_decode(challenge, buf + 2, -1);
2270 challenge[challenge_len] = '\0';
2271 log_print("IMAP< [Decoded: %s]\n", challenge);
2274 md5_hex_hmac(hexdigest, challenge, challenge_len, pass, strlen(pass));
2277 response = g_strdup_printf("%s %s", user, hexdigest);
2278 log_print("IMAP> [Encoded: %s]\n", response);
2279 response64 = g_malloc((strlen(response) + 3) * 2 + 1);
2280 base64_encode(response64, response, strlen(response));
2283 log_print("IMAP> %s\n", response64);
2284 sock_puts(SESSION(session)->sock, response64);
2285 ok = imap_cmd_ok(session, NULL);
2286 if (ok != IMAP_SUCCESS)
2287 log_warning(_("IMAP4 authentication failed.\n"));
2292 static gint imap_cmd_login(IMAPSession *session,
2293 const gchar *user, const gchar *pass)
2295 gchar *user_, *pass_;
2298 QUOTE_IF_REQUIRED(user_, user);
2299 QUOTE_IF_REQUIRED(pass_, pass);
2300 imap_gen_send(session, "LOGIN %s %s", user_, pass_);
2302 ok = imap_cmd_ok(session, NULL);
2303 if (ok != IMAP_SUCCESS)
2304 log_warning(_("IMAP4 login failed.\n"));
2309 static gint imap_cmd_logout(IMAPSession *session)
2311 imap_gen_send(session, "LOGOUT");
2312 return imap_cmd_ok(session, NULL);
2315 /* Send CAPABILITY, and examine the server's response to see whether this
2316 * connection is pre-authenticated or not and build a list of CAPABILITIES. */
2317 static gint imap_greeting(IMAPSession *session)
2322 imap_gen_send(session, "CAPABILITY");
2324 argbuf = g_ptr_array_new();
2326 if (imap_cmd_ok(session, argbuf) != IMAP_SUCCESS ||
2327 ((capstr = search_array_str(argbuf, "CAPABILITY ")) == NULL)) {
2328 ptr_array_free_strings(argbuf);
2329 g_ptr_array_free(argbuf, TRUE);
2333 session->authenticated = search_array_str(argbuf, "PREAUTH") != NULL;
2335 capstr += strlen("CAPABILITY ");
2337 IMAP_SESSION(session)->capability = g_strsplit(capstr, " ", 0);
2339 ptr_array_free_strings(argbuf);
2340 g_ptr_array_free(argbuf, TRUE);
2345 static gboolean imap_has_capability(IMAPSession *session, const gchar *cap)
2349 for (p = session->capability; *p != NULL; ++p)
2350 if (g_strcasecmp(*p, cap) == 0)
2356 void imap_free_capabilities(IMAPSession *session)
2358 g_strfreev(session->capability);
2359 session->capability = NULL;
2362 static const IMAPSet numberlist_to_imapset(MsgNumberList *list)
2364 static GString *imapset = NULL;
2365 static MsgNumberList *numlist, *elem;
2366 guint first, last, next;
2368 if (imapset == NULL)
2369 imapset = g_string_sized_new(256);
2371 g_string_truncate(imapset, 0);
2374 g_slist_free(numlist);
2375 numlist = g_slist_copy(list);
2376 numlist = g_slist_sort(numlist, g_int_compare);
2377 } else if (numlist == NULL) {
2381 first = GPOINTER_TO_INT(numlist->data);
2383 for(elem = g_slist_next(numlist); elem != NULL; elem = g_slist_next(elem)) {
2384 next = GPOINTER_TO_INT(elem->data);
2386 if(next != (last + 1)) {
2387 if (imapset->len > 0)
2388 g_string_append(imapset, ",");
2390 g_string_sprintfa(imapset, "%d", first);
2392 g_string_sprintfa(imapset, "%d:%d", first, last);
2394 if (imapset->len > IMAPCMDLIMIT) {
2404 if (imapset->len > 0)
2405 g_string_append(imapset, ",");
2407 g_string_sprintfa(imapset, "%d", first);
2409 g_string_sprintfa(imapset, "%d:%d", first, last);
2411 g_slist_free(numlist);
2414 MsgNumberList *remaining;
2416 remaining = elem->next;
2417 remaining = g_slist_prepend(remaining, elem->data);
2419 g_slist_free(numlist);
2420 numlist = remaining;
2423 return imapset->str;
2426 static gint imap_cmd_noop(IMAPSession *session)
2428 imap_gen_send(session, "NOOP");
2429 return imap_cmd_ok(session, NULL);
2432 static gint imap_cmd_starttls(IMAPSession *session)
2434 imap_gen_send(session, "STARTTLS");
2435 return imap_cmd_ok(session, NULL);
2438 #define THROW(err) { ok = err; goto catch; }
2440 static gint imap_cmd_namespace(IMAPSession *session, gchar **ns_str)
2446 argbuf = g_ptr_array_new();
2448 imap_gen_send(session, "NAMESPACE");
2449 if ((ok = imap_cmd_ok(session, argbuf)) != IMAP_SUCCESS) THROW(ok);
2451 str = search_array_str(argbuf, "NAMESPACE");
2452 if (!str) THROW(IMAP_ERROR);
2454 *ns_str = g_strdup(str);
2457 ptr_array_free_strings(argbuf);
2458 g_ptr_array_free(argbuf, TRUE);
2465 static gint imap_cmd_list(IMAPSession *session, const gchar *ref,
2466 const gchar *mailbox, GPtrArray *argbuf)
2468 gchar *ref_, *mailbox_;
2470 if (!ref) ref = "\"\"";
2471 if (!mailbox) mailbox = "\"\"";
2473 QUOTE_IF_REQUIRED(ref_, ref);
2474 QUOTE_IF_REQUIRED(mailbox_, mailbox);
2475 imap_gen_send(session, "LIST %s %s", ref_, mailbox_);
2477 return imap_cmd_ok(session, argbuf);
2480 #define THROW goto catch
2482 static gint imap_cmd_do_select(IMAPSession *session, const gchar *folder,
2484 gint *exists, gint *recent, gint *unseen,
2485 guint32 *uid_validity)
2493 *exists = *recent = *unseen = *uid_validity = 0;
2494 argbuf = g_ptr_array_new();
2497 select_cmd = "EXAMINE";
2499 select_cmd = "SELECT";
2501 QUOTE_IF_REQUIRED(folder_, folder);
2502 imap_gen_send(session, "%s %s", select_cmd, folder_);
2504 if ((ok = imap_cmd_ok(session, argbuf)) != IMAP_SUCCESS) THROW;
2506 resp_str = search_array_contain_str(argbuf, "EXISTS");
2508 if (sscanf(resp_str,"%d EXISTS", exists) != 1) {
2509 g_warning("imap_cmd_select(): invalid EXISTS line.\n");
2514 resp_str = search_array_contain_str(argbuf, "RECENT");
2516 if (sscanf(resp_str, "%d RECENT", recent) != 1) {
2517 g_warning("imap_cmd_select(): invalid RECENT line.\n");
2522 resp_str = search_array_contain_str(argbuf, "UIDVALIDITY");
2524 if (sscanf(resp_str, "OK [UIDVALIDITY %u] ", uid_validity)
2526 g_warning("imap_cmd_select(): invalid UIDVALIDITY line.\n");
2531 resp_str = search_array_contain_str(argbuf, "UNSEEN");
2533 if (sscanf(resp_str, "OK [UNSEEN %d] ", unseen) != 1) {
2534 g_warning("imap_cmd_select(): invalid UNSEEN line.\n");
2540 ptr_array_free_strings(argbuf);
2541 g_ptr_array_free(argbuf, TRUE);
2546 static gint imap_cmd_select(IMAPSession *session, const gchar *folder,
2547 gint *exists, gint *recent, gint *unseen,
2548 guint32 *uid_validity)
2550 return imap_cmd_do_select(session, folder, FALSE,
2551 exists, recent, unseen, uid_validity);
2554 static gint imap_cmd_examine(IMAPSession *session, const gchar *folder,
2555 gint *exists, gint *recent, gint *unseen,
2556 guint32 *uid_validity)
2558 return imap_cmd_do_select(session, folder, TRUE,
2559 exists, recent, unseen, uid_validity);
2564 static gint imap_cmd_create(IMAPSession *session, const gchar *folder)
2568 QUOTE_IF_REQUIRED(folder_, folder);
2569 imap_gen_send(session, "CREATE %s", folder_);
2571 return imap_cmd_ok(session, NULL);
2574 static gint imap_cmd_rename(IMAPSession *session, const gchar *old_folder,
2575 const gchar *new_folder)
2577 gchar *old_folder_, *new_folder_;
2579 QUOTE_IF_REQUIRED(old_folder_, old_folder);
2580 QUOTE_IF_REQUIRED(new_folder_, new_folder);
2581 imap_gen_send(session, "RENAME %s %s", old_folder_, new_folder_);
2583 return imap_cmd_ok(session, NULL);
2586 static gint imap_cmd_delete(IMAPSession *session, const gchar *folder)
2590 QUOTE_IF_REQUIRED(folder_, folder);
2591 imap_gen_send(session, "DELETE %s", folder_);
2593 return imap_cmd_ok(session, NULL);
2596 static gint imap_cmd_search(IMAPSession *session, const gchar *criteria, GSList **list)
2602 g_return_val_if_fail(criteria != NULL, IMAP_ERROR);
2603 g_return_val_if_fail(list != NULL, IMAP_ERROR);
2607 argbuf = g_ptr_array_new();
2608 imap_gen_send(session, "UID SEARCH %s", criteria);
2610 ok = imap_cmd_ok(session, argbuf);
2611 if (ok != IMAP_SUCCESS) {
2612 ptr_array_free_strings(argbuf);
2613 g_ptr_array_free(argbuf, TRUE);
2617 if ((uidlist = search_array_str(argbuf, "SEARCH ")) != NULL) {
2618 gchar **strlist, **p;
2620 strlist = g_strsplit(uidlist + 7, " ", 0);
2621 for (p = strlist; *p != NULL; ++p) {
2624 if (sscanf(*p, "%d", &msgnum) == 1)
2625 *list = g_slist_append(*list, GINT_TO_POINTER(msgnum));
2627 g_strfreev(strlist);
2629 ptr_array_free_strings(argbuf);
2630 g_ptr_array_free(argbuf, TRUE);
2632 return IMAP_SUCCESS;
2635 static gint imap_cmd_fetch(IMAPSession *session, guint32 uid, const gchar *filename)
2643 g_return_val_if_fail(filename != NULL, IMAP_ERROR);
2645 imap_gen_send(session, "UID FETCH %d BODY.PEEK[]", uid);
2647 while ((ok = imap_gen_recv(session, &buf))
2649 if (buf[0] != '*' || buf[1] != ' ') {
2653 if (strstr(buf, "FETCH") != NULL)
2657 if (ok != IMAP_SUCCESS) {
2662 cur_pos = strchr(buf, '{');
2663 if (cur_pos == NULL) {
2668 cur_pos = strchr_cpy(cur_pos + 1, '}', size_str, sizeof(size_str));
2669 if (cur_pos == NULL) {
2673 size_num = atol(size_str);
2674 g_return_val_if_fail(size_num > 0, IMAP_ERROR);
2676 if (*cur_pos != '\0') {
2681 if (recv_bytes_write_to_file(SESSION(session)->sock, size_num, filename) != 0) {
2686 if (imap_gen_recv(session, &buf) != IMAP_SUCCESS) {
2691 if (buf[0] == '\0' || buf[strlen(buf) - 1] != ')') {
2697 ok = imap_cmd_ok(session, NULL);
2702 static gint imap_cmd_append(IMAPSession *session, const gchar *destfolder,
2703 const gchar *file, gint32 *new_uid)
2708 gchar buf[BUFFSIZE], *imapbuf;
2713 g_return_val_if_fail(file != NULL, IMAP_ERROR);
2715 size = get_file_size_as_crlf(file);
2716 if ((fp = fopen(file, "rb")) == NULL) {
2717 FILE_OP_ERROR(file, "fopen");
2720 QUOTE_IF_REQUIRED(destfolder_, destfolder);
2721 imap_gen_send(session, "APPEND %s (\\Seen) {%d}", destfolder_, size);
2723 ok = imap_gen_recv(session, &imapbuf);
2724 if (ok != IMAP_SUCCESS || imapbuf[0] != '+' || imapbuf[1] != ' ') {
2725 log_warning(_("can't append %s to %s\n"), file, destfolder_);
2732 log_print("IMAP4> %s\n", _("(sending file...)"));
2734 while (fgets(buf, sizeof(buf), fp) != NULL) {
2736 if (sock_puts(SESSION(session)->sock, buf) < 0) {
2743 FILE_OP_ERROR(file, "fgets");
2748 sock_puts(SESSION(session)->sock, "");
2752 reply = g_ptr_array_new();
2755 ok = imap_cmd_ok(session, reply);
2756 if (ok != IMAP_SUCCESS)
2757 log_warning(_("can't append message to %s\n"), destfolder_);
2759 (new_uid != NULL) &&
2760 (imap_has_capability(session, "UIDPLUS") && reply->len > 0) &&
2761 ((okmsginfo = g_ptr_array_index(reply, reply->len - 1)) != NULL) &&
2762 (sscanf(okmsginfo, "%*u OK [APPENDUID %*u %u]", &newuid) == 1)) {
2766 ptr_array_free_strings(reply);
2767 g_ptr_array_free(reply, TRUE);
2772 static gint imap_cmd_copy(IMAPSession * session,
2774 const gchar * destfolder, gint32 * new_uid)
2777 gint32 olduid, newuid;
2782 g_return_val_if_fail(destfolder != NULL, IMAP_ERROR);
2783 g_return_val_if_fail(session != NULL, IMAP_ERROR);
2784 g_return_val_if_fail(new_uid != NULL, IMAP_ERROR);
2786 QUOTE_IF_REQUIRED(destfolder_, destfolder);
2787 imap_gen_send(session, "UID COPY %d %s", msgnum, destfolder_);
2789 reply = g_ptr_array_new();
2792 ok = imap_cmd_ok(session, reply);
2793 if (ok != IMAP_SUCCESS)
2794 log_warning(_("can't copy %d to %s\n"), msgnum, destfolder_);
2795 else if (imap_has_capability(session, "UIDPLUS") && reply->len > 0)
2796 if ((okmsginfo = g_ptr_array_index(reply, reply->len - 1)) != NULL &&
2797 sscanf(okmsginfo, "%*u OK [COPYUID %*u %u %u]", &olduid, &newuid) == 2 &&
2801 ptr_array_free_strings(reply);
2802 g_ptr_array_free(reply, TRUE);
2806 gint imap_cmd_envelope(IMAPSession *session, IMAPSet set)
2808 static GString *header_fields = NULL;
2810 if (header_fields == NULL) {
2811 const HeaderEntry *headers, *elem;
2813 headers = procheader_get_headernames(FALSE);
2814 header_fields = g_string_new("");
2816 for (elem = headers; elem->name != NULL; ++elem) {
2817 gint namelen = strlen(elem->name);
2819 /* Header fields ending with space are not rfc822 headers */
2820 if (elem->name[namelen - 1] == ' ')
2823 /* strip : at the of header field */
2824 if(elem->name[namelen - 1] == ':')
2830 g_string_sprintfa(header_fields, "%s%.*s",
2831 header_fields->str[0] != '\0' ? " " : "",
2832 namelen, elem->name);
2837 (session, "UID FETCH %s (UID FLAGS RFC822.SIZE BODY.PEEK[HEADER.FIELDS (%s)])",
2838 set, header_fields->str);
2840 return IMAP_SUCCESS;
2843 static gint imap_cmd_store(IMAPSession *session, IMAPSet set,
2848 imap_gen_send(session, "UID STORE %s %s",
2851 if ((ok = imap_cmd_ok(session, NULL)) != IMAP_SUCCESS) {
2852 log_warning(_("error while imap command: STORE %s %s\n"),
2857 return IMAP_SUCCESS;
2860 static gint imap_cmd_expunge(IMAPSession *session)
2864 imap_gen_send(session, "EXPUNGE");
2865 if ((ok = imap_cmd_ok(session, NULL)) != IMAP_SUCCESS) {
2866 log_warning(_("error while imap command: EXPUNGE\n"));
2870 return IMAP_SUCCESS;
2873 static gint imap_cmd_ok(IMAPSession *session, GPtrArray *argbuf)
2875 gint ok = IMAP_SUCCESS;
2880 while ((ok = imap_gen_recv(session, &buf))
2882 // make sure data is long enough for any substring of buf
2883 data = alloca(strlen(buf) + 1);
2885 // untagged line read
2886 if (buf[0] == '*' && buf[1] == ' ') {
2889 g_ptr_array_add(argbuf, g_strdup(buf + 2));
2891 if (sscanf(buf + 2, "%d %s", &num, data) >= 2) {
2892 if (!strcmp(data, "EXISTS")) {
2893 session->exists = num;
2894 session->folder_content_changed = TRUE;
2897 if(!strcmp(data, "EXPUNGE")) {
2899 session->folder_content_changed = TRUE;
2902 // tagged line with correct tag and OK response found
2903 } else if ((sscanf(buf, "%d %s", &cmd_num, data) >= 2) &&
2904 (cmd_num == session->cmd_count) &&
2905 !strcmp(data, "OK")) {
2907 g_ptr_array_add(argbuf, g_strdup(buf));
2921 static void imap_gen_send(IMAPSession *session, const gchar *format, ...)
2928 va_start(args, format);
2929 tmp = g_strdup_vprintf(format, args);
2932 session->cmd_count++;
2934 buf = g_strdup_printf("%d %s\r\n", session->cmd_count, tmp);
2935 if (!strncasecmp(tmp, "LOGIN ", 6) && (p = strchr(tmp + 6, ' '))) {
2937 log_print("IMAP4> %d %s ********\n", session->cmd_count, tmp);
2939 log_print("IMAP4> %d %s\n", session->cmd_count, tmp);
2941 sock_write_all(SESSION(session)->sock, buf, strlen(buf));
2946 static gint imap_gen_recv(IMAPSession *session, gchar **buf)
2948 if ((*buf = sock_getline(SESSION(session)->sock)) == NULL)
2953 log_print("IMAP4< %s\n", *buf);
2955 return IMAP_SUCCESS;
2959 /* misc utility functions */
2961 static gchar *strchr_cpy(const gchar *src, gchar ch, gchar *dest, gint len)
2966 tmp = strchr(src, ch);
2970 memcpy(dest, src, MIN(tmp - src, len - 1));
2971 dest[MIN(tmp - src, len - 1)] = '\0';
2976 static gchar *get_quoted(const gchar *src, gchar ch, gchar *dest, gint len)
2978 const gchar *p = src;
2981 g_return_val_if_fail(*p == ch, NULL);
2986 while (*p != '\0' && *p != ch) {
2988 if (*p == '\\' && *(p + 1) != '\0')
2997 return (gchar *)(*p == ch ? p + 1 : p);
3000 static gchar *search_array_contain_str(GPtrArray *array, const gchar *str)
3004 for (i = 0; i < array->len; i++) {
3007 tmp = g_ptr_array_index(array, i);
3008 if (strstr(tmp, str) != NULL)
3015 static gchar *search_array_str(GPtrArray *array, const gchar *str)
3022 for (i = 0; i < array->len; i++) {
3025 tmp = g_ptr_array_index(array, i);
3026 if (!strncmp(tmp, str, len))
3033 static void imap_path_separator_subst(gchar *str, gchar separator)
3036 gboolean in_escape = FALSE;
3038 if (!separator || separator == '/') return;
3040 for (p = str; *p != '\0'; p++) {
3041 if (*p == '/' && !in_escape)
3043 else if (*p == '&' && *(p + 1) != '-' && !in_escape)
3045 else if (*p == '-' && in_escape)
3050 static gchar *imap_modified_utf7_to_locale(const gchar *mutf7_str)
3053 const gchar *from_p;
3056 to = g_malloc(strlen(mutf7_str) + 1);
3059 for (from_p = mutf7_str; *from_p != '\0'; from_p++) {
3060 if (*from_p == '&' && *(from_p + 1) == '-') {
3070 static iconv_t cd = (iconv_t)-1;
3071 static gboolean iconv_ok = TRUE;
3074 size_t norm_utf7_len;
3076 gchar *to_str, *to_p;
3078 gboolean in_escape = FALSE;
3080 if (!iconv_ok) return g_strdup(mutf7_str);
3082 if (cd == (iconv_t)-1) {
3083 cd = iconv_open(conv_get_current_charset_str(), "UTF-7");
3084 if (cd == (iconv_t)-1) {
3085 g_warning("iconv cannot convert UTF-7 to %s\n",
3086 conv_get_current_charset_str());
3088 return g_strdup(mutf7_str);
3092 norm_utf7 = g_string_new(NULL);
3094 for (p = mutf7_str; *p != '\0'; p++) {
3095 /* replace: '&' -> '+',
3097 escaped ',' -> '/' */
3098 if (!in_escape && *p == '&') {
3099 if (*(p + 1) != '-') {
3100 g_string_append_c(norm_utf7, '+');
3103 g_string_append_c(norm_utf7, '&');
3106 } else if (in_escape && *p == ',') {
3107 g_string_append_c(norm_utf7, '/');
3108 } else if (in_escape && *p == '-') {
3109 g_string_append_c(norm_utf7, '-');
3112 g_string_append_c(norm_utf7, *p);
3116 norm_utf7_p = norm_utf7->str;
3117 norm_utf7_len = norm_utf7->len;
3118 to_len = strlen(mutf7_str) * 5;
3119 to_p = to_str = g_malloc(to_len + 1);
3121 if (iconv(cd, (ICONV_CONST gchar **)&norm_utf7_p, &norm_utf7_len,
3122 &to_p, &to_len) == -1) {
3123 g_warning(_("iconv cannot convert UTF-7 to %s\n"),
3124 conv_get_current_charset_str());
3125 g_string_free(norm_utf7, TRUE);
3127 return g_strdup(mutf7_str);
3130 /* second iconv() call for flushing */
3131 iconv(cd, NULL, NULL, &to_p, &to_len);
3132 g_string_free(norm_utf7, TRUE);
3136 #endif /* !HAVE_ICONV */
3139 static gchar *imap_locale_to_modified_utf7(const gchar *from)
3142 const gchar *from_p;
3145 to = g_malloc(strlen(from) * 2 + 1);
3148 for (from_p = from; *from_p != '\0'; from_p++) {
3149 if (*from_p == '&') {
3159 static iconv_t cd = (iconv_t)-1;
3160 static gboolean iconv_ok = TRUE;
3161 gchar *norm_utf7, *norm_utf7_p;
3162 size_t from_len, norm_utf7_len;
3164 gchar *from_tmp, *to, *p;
3165 gboolean in_escape = FALSE;
3167 if (!iconv_ok) return g_strdup(from);
3169 if (cd == (iconv_t)-1) {
3170 cd = iconv_open("UTF-7", conv_get_current_charset_str());
3171 if (cd == (iconv_t)-1) {
3172 g_warning("iconv cannot convert %s to UTF-7\n",
3173 conv_get_current_charset_str());
3175 return g_strdup(from);
3179 Xstrdup_a(from_tmp, from, return g_strdup(from));
3180 from_len = strlen(from);
3181 norm_utf7_len = from_len * 5;
3182 Xalloca(norm_utf7, norm_utf7_len + 1, return g_strdup(from));
3183 norm_utf7_p = norm_utf7;
3185 #define IS_PRINT(ch) (isprint(ch) && isascii(ch))
3187 while (from_len > 0) {
3188 if (*from_tmp == '+') {
3189 *norm_utf7_p++ = '+';
3190 *norm_utf7_p++ = '-';
3194 } else if (IS_PRINT(*from_tmp)) {
3195 /* printable ascii char */
3196 *norm_utf7_p = *from_tmp;
3202 size_t mb_len = 0, conv_len = 0;
3204 /* unprintable char: convert to UTF-7 */
3206 while (!IS_PRINT(*p) && conv_len < from_len) {
3207 mb_len = mblen(p, MB_LEN_MAX);
3209 g_warning("wrong multibyte sequence\n");
3210 return g_strdup(from);
3216 from_len -= conv_len;
3217 if (iconv(cd, (ICONV_CONST gchar **)&from_tmp,
3219 &norm_utf7_p, &norm_utf7_len) == -1) {
3220 g_warning("iconv cannot convert %s to UTF-7\n",
3221 conv_get_current_charset_str());
3222 return g_strdup(from);
3225 /* second iconv() call for flushing */
3226 iconv(cd, NULL, NULL, &norm_utf7_p, &norm_utf7_len);
3232 *norm_utf7_p = '\0';
3233 to_str = g_string_new(NULL);
3234 for (p = norm_utf7; p < norm_utf7_p; p++) {
3235 /* replace: '&' -> "&-",
3238 BASE64 '/' -> ',' */
3239 if (!in_escape && *p == '&') {
3240 g_string_append(to_str, "&-");
3241 } else if (!in_escape && *p == '+') {
3242 if (*(p + 1) == '-') {
3243 g_string_append_c(to_str, '+');
3246 g_string_append_c(to_str, '&');
3249 } else if (in_escape && *p == '/') {
3250 g_string_append_c(to_str, ',');
3251 } else if (in_escape && *p == '-') {
3252 g_string_append_c(to_str, '-');
3255 g_string_append_c(to_str, *p);
3261 g_string_append_c(to_str, '-');
3265 g_string_free(to_str, FALSE);
3268 #endif /* !HAVE_ICONV */
3271 static gboolean imap_rename_folder_func(GNode *node, gpointer data)
3273 FolderItem *item = node->data;
3274 gchar **paths = data;
3275 const gchar *oldpath = paths[0];
3276 const gchar *newpath = paths[1];
3278 gchar *new_itempath;
3281 oldpathlen = strlen(oldpath);
3282 if (strncmp(oldpath, item->path, oldpathlen) != 0) {
3283 g_warning("path doesn't match: %s, %s\n", oldpath, item->path);
3287 base = item->path + oldpathlen;
3288 while (*base == G_DIR_SEPARATOR) base++;
3290 new_itempath = g_strdup(newpath);
3292 new_itempath = g_strconcat(newpath, G_DIR_SEPARATOR_S, base,
3295 item->path = new_itempath;
3300 static gint get_list_of_uids(Folder *folder, IMAPFolderItem *item, GSList **msgnum_list)
3302 gint ok, nummsgs = 0, lastuid_old;
3303 IMAPSession *session;
3304 GSList *uidlist, *elem;
3307 session = imap_session_get(folder);
3308 g_return_val_if_fail(session != NULL, -1);
3310 ok = imap_select(session, IMAP_FOLDER(folder), item->item.path,
3311 NULL, NULL, NULL, NULL);
3312 if (ok != IMAP_SUCCESS)
3315 cmd_buf = g_strdup_printf("UID %d:*", item->lastuid + 1);
3316 ok = imap_cmd_search(session, cmd_buf, &uidlist);
3319 if (ok == IMAP_SOCKET) {
3320 session_destroy((Session *)session);
3321 ((RemoteFolder *)folder)->session = NULL;
3325 if (ok != IMAP_SUCCESS) {
3329 argbuf = g_ptr_array_new();
3331 cmd_buf = g_strdup_printf("UID FETCH %d:* (UID)", item->lastuid + 1);
3332 imap_gen_send(session, cmd_buf);
3334 ok = imap_cmd_ok(session, argbuf);
3335 if (ok != IMAP_SUCCESS) {
3336 ptr_array_free_strings(argbuf);
3337 g_ptr_array_free(argbuf, TRUE);
3341 for(i = 0; i < argbuf->len; i++) {
3344 if((ret = sscanf(g_ptr_array_index(argbuf, i),
3345 "%*d FETCH (UID %d)", &msgnum)) == 1)
3346 uidlist = g_slist_prepend(uidlist, GINT_TO_POINTER(msgnum));
3348 ptr_array_free_strings(argbuf);
3349 g_ptr_array_free(argbuf, TRUE);
3352 lastuid_old = item->lastuid;
3353 *msgnum_list = g_slist_copy(item->uid_list);
3354 nummsgs = g_slist_length(*msgnum_list);
3355 debug_print("Got %d uids from cache\n", g_slist_length(item->uid_list));
3357 for (elem = uidlist; elem != NULL; elem = g_slist_next(elem)) {
3360 msgnum = GPOINTER_TO_INT(elem->data);
3361 if (msgnum > lastuid_old) {
3362 *msgnum_list = g_slist_prepend(*msgnum_list, GINT_TO_POINTER(msgnum));
3363 item->uid_list = g_slist_prepend(item->uid_list, GINT_TO_POINTER(msgnum));
3366 if(msgnum > item->lastuid)
3367 item->lastuid = msgnum;
3370 g_slist_free(uidlist);
3375 gint imap_get_num_list(Folder *folder, FolderItem *_item, GSList **msgnum_list)
3377 IMAPFolderItem *item = (IMAPFolderItem *)_item;
3378 IMAPSession *session;
3379 gint ok, nummsgs = 0, exists, recent, unseen, uid_val, uid_next;
3382 gboolean selected_folder;
3384 g_return_val_if_fail(folder != NULL, -1);
3385 g_return_val_if_fail(item != NULL, -1);
3386 g_return_val_if_fail(item->item.path != NULL, -1);
3387 g_return_val_if_fail(FOLDER_CLASS(folder) == &imap_class, -1);
3388 g_return_val_if_fail(folder->account != NULL, -1);
3390 session = imap_session_get(folder);
3391 g_return_val_if_fail(session != NULL, -1);
3393 selected_folder = (session->mbox != NULL) &&
3394 (!strcmp(session->mbox, item->item.path));
3395 if (selected_folder) {
3396 ok = imap_cmd_noop(session);
3397 if (ok != IMAP_SUCCESS)
3399 exists = session->exists;
3401 ok = imap_status(session, IMAP_FOLDER(folder), item->item.path,
3402 &exists, &recent, &uid_next, &uid_val, &unseen);
3403 if (ok != IMAP_SUCCESS)
3407 /* If old uid_next matches new uid_next we can be sure no message
3408 was added to the folder */
3409 if (( selected_folder && !session->folder_content_changed) ||
3410 (!selected_folder && uid_next == item->uid_next)) {
3411 nummsgs = g_slist_length(item->uid_list);
3413 /* If number of messages is still the same we
3414 know our caches message numbers are still valid,
3415 otherwise if the number of messages has decrease
3416 we discard our cache to start a new scan to find
3417 out which numbers have been removed */
3418 if (exists == nummsgs) {
3419 *msgnum_list = g_slist_copy(item->uid_list);
3421 } else if (exists < nummsgs) {
3422 debug_print("Freeing imap uid cache");
3424 g_slist_free(item->uid_list);
3425 item->uid_list = NULL;
3428 if (!selected_folder)
3429 item->uid_next = uid_next;
3432 *msgnum_list = NULL;
3436 nummsgs = get_list_of_uids(folder, item, &uidlist);
3438 if (nummsgs != exists) {
3439 /* Cache contains more messages then folder, we have cached
3440 an old UID of a message that was removed and new messages
3441 have been added too, otherwise the uid_next check would
3443 debug_print("Freeing imap uid cache");
3445 g_slist_free(item->uid_list);
3446 item->uid_list = NULL;
3448 g_slist_free(*msgnum_list);
3450 nummsgs = get_list_of_uids(folder, item, &uidlist);
3453 *msgnum_list = uidlist;
3455 dir = folder_item_get_path((FolderItem *)item);
3456 debug_print("removing old messages from %s\n", dir);
3457 remove_numbered_files_not_in_list(dir, *msgnum_list);
3463 static MsgInfo *imap_parse_msg(const gchar *file, FolderItem *item)
3468 flags.perm_flags = MSG_NEW|MSG_UNREAD;
3469 flags.tmp_flags = 0;
3471 g_return_val_if_fail(item != NULL, NULL);
3472 g_return_val_if_fail(file != NULL, NULL);
3474 if (item->stype == F_QUEUE) {
3475 MSG_SET_TMP_FLAGS(flags, MSG_QUEUED);
3476 } else if (item->stype == F_DRAFT) {
3477 MSG_SET_TMP_FLAGS(flags, MSG_DRAFT);
3480 msginfo = procheader_parse_file(file, flags, FALSE, FALSE);
3481 if (!msginfo) return NULL;
3483 msginfo->folder = item;
3488 GSList *imap_get_msginfos(Folder *folder, FolderItem *item, GSList *msgnum_list)
3490 IMAPSession *session;
3491 MsgInfoList *ret = NULL;
3494 g_return_val_if_fail(folder != NULL, NULL);
3495 g_return_val_if_fail(item != NULL, NULL);
3496 g_return_val_if_fail(msgnum_list != NULL, NULL);
3498 session = imap_session_get(folder);
3499 g_return_val_if_fail(session != NULL, NULL);
3501 ok = imap_select(session, IMAP_FOLDER(folder), item->path,
3502 NULL, NULL, NULL, NULL);
3503 if (ok != IMAP_SUCCESS)
3506 if (!(item->stype == F_QUEUE || item->stype == F_DRAFT)) {
3507 ret = g_slist_concat(ret,
3508 imap_get_uncached_messages(
3509 session, item, msgnum_list));
3511 MsgNumberList *sorted_list, *elem;
3512 gint startnum, lastnum;
3514 sorted_list = g_slist_sort(g_slist_copy(msgnum_list), g_int_compare);
3516 startnum = lastnum = GPOINTER_TO_INT(sorted_list->data);
3518 for (elem = sorted_list;; elem = g_slist_next(elem)) {
3522 num = GPOINTER_TO_INT(elem->data);
3524 if (num > lastnum + 1 || elem == NULL) {
3526 for (i = startnum; i <= lastnum; ++i) {
3529 file = imap_fetch_msg(folder, item, i);
3531 MsgInfo *msginfo = imap_parse_msg(file, item);
3532 if (msginfo != NULL) {
3533 msginfo->msgnum = i;
3534 ret = g_slist_append(ret, msginfo);
3548 g_slist_free(sorted_list);
3554 MsgInfo *imap_get_msginfo(Folder *folder, FolderItem *item, gint uid)
3556 MsgInfo *msginfo = NULL;
3557 MsgInfoList *msginfolist;
3558 MsgNumberList numlist;
3560 numlist.next = NULL;
3561 numlist.data = GINT_TO_POINTER(uid);
3563 msginfolist = imap_get_msginfos(folder, item, &numlist);
3564 if (msginfolist != NULL) {
3565 msginfo = msginfolist->data;
3566 g_slist_free(msginfolist);
3572 gboolean imap_check_msgnum_validity(Folder *folder, FolderItem *_item)
3574 IMAPSession *session;
3575 IMAPFolderItem *item = (IMAPFolderItem *)_item;
3576 gint ok, exists = 0, recent = 0, unseen = 0;
3577 guint32 uid_next, uid_validity = 0;
3579 g_return_val_if_fail(folder != NULL, FALSE);
3580 g_return_val_if_fail(item != NULL, FALSE);
3581 g_return_val_if_fail(item->item.folder != NULL, FALSE);
3582 g_return_val_if_fail(FOLDER_CLASS(item->item.folder) == &imap_class, FALSE);
3584 session = imap_session_get(folder);
3585 g_return_val_if_fail(session != NULL, FALSE);
3587 ok = imap_status(session, IMAP_FOLDER(folder), item->item.path,
3588 &exists, &recent, &uid_next, &uid_validity, &unseen);
3589 if (ok != IMAP_SUCCESS)
3592 if(item->item.mtime == uid_validity)
3595 debug_print("Freeing imap uid cache\n");
3597 g_slist_free(item->uid_list);
3598 item->uid_list = NULL;
3600 item->item.mtime = uid_validity;
3602 imap_delete_all_cached_messages((FolderItem *)item);
3607 void imap_change_flags(Folder *folder, FolderItem *item, MsgInfo *msginfo, MsgPermFlags newflags)
3609 IMAPSession *session;
3610 IMAPFlags flags_set = 0, flags_unset = 0;
3611 gint ok = IMAP_SUCCESS;
3612 MsgNumberList numlist;
3614 g_return_if_fail(folder != NULL);
3615 g_return_if_fail(folder->klass == &imap_class);
3616 g_return_if_fail(item != NULL);
3617 g_return_if_fail(item->folder == folder);
3618 g_return_if_fail(msginfo != NULL);
3619 g_return_if_fail(msginfo->folder == item);
3621 session = imap_session_get(folder);
3622 if (!session) return;
3624 if ((ok = imap_select(session, IMAP_FOLDER(folder), msginfo->folder->path,
3625 NULL, NULL, NULL, NULL)) != IMAP_SUCCESS)
3628 if (!MSG_IS_MARKED(msginfo->flags) && (newflags & MSG_MARKED))
3629 flags_set |= IMAP_FLAG_FLAGGED;
3630 if ( MSG_IS_MARKED(msginfo->flags) && !(newflags & MSG_MARKED))
3631 flags_unset |= IMAP_FLAG_FLAGGED;
3633 if (!MSG_IS_UNREAD(msginfo->flags) && (newflags & MSG_UNREAD))
3634 flags_unset |= IMAP_FLAG_SEEN;
3635 if ( MSG_IS_UNREAD(msginfo->flags) && !(newflags & MSG_UNREAD))
3636 flags_set |= IMAP_FLAG_SEEN;
3638 if (!MSG_IS_REPLIED(msginfo->flags) && (newflags & MSG_REPLIED))
3639 flags_set |= IMAP_FLAG_ANSWERED;
3640 if ( MSG_IS_REPLIED(msginfo->flags) && !(newflags & MSG_REPLIED))
3641 flags_set |= IMAP_FLAG_ANSWERED;
3643 numlist.next = NULL;
3644 numlist.data = GINT_TO_POINTER(msginfo->msgnum);
3647 ok = imap_set_message_flags(session, &numlist, flags_set, TRUE);
3648 if (ok != IMAP_SUCCESS) return;
3652 ok = imap_set_message_flags(session, &numlist, flags_unset, FALSE);
3653 if (ok != IMAP_SUCCESS) return;
3656 msginfo->flags.perm_flags = newflags;