2 * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2003 Hiroyuki Yamamoto
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
49 #include "procheader.h"
50 #include "prefs_account.h"
53 #include "inputdialog.h"
56 typedef struct _IMAPFolder IMAPFolder;
57 typedef struct _IMAPSession IMAPSession;
58 typedef struct _IMAPNameSpace IMAPNameSpace;
59 typedef struct _IMAPFolderItem IMAPFolderItem;
61 #include "prefs_account.h"
63 #define IMAP_FOLDER(obj) ((IMAPFolder *)obj)
64 #define IMAP_SESSION(obj) ((IMAPSession *)obj)
70 /* list of IMAPNameSpace */
82 time_t last_access_time;
83 gboolean authenticated;
85 gboolean folder_content_changed;
95 #define IMAP_SUCCESS 0
97 #define IMAP_AUTHFAIL 3
98 #define IMAP_PROTOCOL 4
103 #define IMAPBUFSIZE 8192
107 IMAP_FLAG_SEEN = 1 << 0,
108 IMAP_FLAG_ANSWERED = 1 << 1,
109 IMAP_FLAG_FLAGGED = 1 << 2,
110 IMAP_FLAG_DELETED = 1 << 3,
111 IMAP_FLAG_DRAFT = 1 << 4
114 #define IMAP_IS_SEEN(flags) ((flags & IMAP_FLAG_SEEN) != 0)
115 #define IMAP_IS_ANSWERED(flags) ((flags & IMAP_FLAG_ANSWERED) != 0)
116 #define IMAP_IS_FLAGGED(flags) ((flags & IMAP_FLAG_FLAGGED) != 0)
117 #define IMAP_IS_DELETED(flags) ((flags & IMAP_FLAG_DELETED) != 0)
118 #define IMAP_IS_DRAFT(flags) ((flags & IMAP_FLAG_DRAFT) != 0)
121 #define IMAP4_PORT 143
123 #define IMAPS_PORT 993
126 #define QUOTE_IF_REQUIRED(out, str) \
128 if (*str != '"' && strpbrk(str, " \t(){}%*") != NULL) { \
132 len = strlen(str) + 3; \
133 Xalloca(__tmp, len, return IMAP_ERROR); \
134 g_snprintf(__tmp, len, "\"%s\"", str); \
137 Xstrdup_a(out, str, return IMAP_ERROR); \
141 typedef gchar * IMAPSet;
143 struct _IMAPFolderItem
152 static Folder *imap_folder_new(const gchar * name, const gchar * path);
153 static void imap_folder_destroy(Folder * folder);
155 static IMAPSession *imap_session_new(const PrefsAccount * account);
156 static void imap_session_authenticate(IMAPSession * session,
157 const PrefsAccount * account);
158 static void imap_session_destroy(Session * session);
160 static gchar *imap_fetch_msg(Folder * folder, FolderItem * item, gint uid);
161 static gint imap_add_msg(Folder * folder,
163 const gchar * file, gboolean remove_source);
165 static gint imap_copy_msg(Folder * folder,
166 FolderItem * dest, MsgInfo * msginfo);
168 static gint imap_remove_msg(Folder * folder, FolderItem * item, gint uid);
169 static gint imap_remove_all_msg(Folder * folder, FolderItem * item);
171 static gboolean imap_is_msg_changed(Folder * folder,
172 FolderItem * item, MsgInfo * msginfo);
174 static void imap_scan_tree(Folder * folder);
176 static gint imap_create_tree(Folder * folder);
178 static FolderItem *imap_create_folder(Folder * folder,
181 static gint imap_rename_folder(Folder * folder,
182 FolderItem * item, const gchar * name);
183 static gint imap_remove_folder(Folder * folder, FolderItem * item);
186 static void imap_folder_init (Folder *folder,
190 static FolderItem *imap_folder_item_new (Folder *folder);
191 static void imap_folder_item_destroy (Folder *folder,
194 static IMAPSession *imap_session_get (Folder *folder);
196 static gint imap_scan_tree_recursive (IMAPSession *session,
198 static GSList *imap_parse_list (Folder *folder,
199 IMAPSession *session,
200 const gchar *real_path,
203 static void imap_create_missing_folders (Folder *folder);
204 static FolderItem *imap_create_special_folder
206 SpecialFolderItemType stype,
209 static gint imap_do_copy (Folder *folder,
212 gboolean remove_source);
214 static void imap_delete_all_cached_messages (FolderItem *item);
217 static SockInfo *imap_open (const gchar *server,
221 static SockInfo *imap_open (const gchar *server,
226 static SockInfo *imap_open_tunnel(const gchar *server,
227 const gchar *tunnelcmd,
230 static SockInfo *imap_open_tunnel(const gchar *server,
231 const gchar *tunnelcmd);
235 static SockInfo *imap_init_sock(SockInfo *sock, SSLType ssl_type);
237 static SockInfo *imap_init_sock(SockInfo *sock);
240 static gint imap_set_message_flags (IMAPSession *session,
241 MsgNumberList *numlist,
244 static gint imap_select (IMAPSession *session,
250 guint32 *uid_validity);
251 static gint imap_status (IMAPSession *session,
257 guint32 *uid_validity,
260 static void imap_parse_namespace (IMAPSession *session,
262 static void imap_get_namespace_by_list (IMAPSession *session,
264 static IMAPNameSpace *imap_find_namespace (IMAPFolder *folder,
266 static gchar imap_get_path_separator (IMAPFolder *folder,
268 static gchar *imap_get_real_path (IMAPFolder *folder,
271 static gchar *imap_parse_atom (SockInfo *sock,
276 static MsgFlags imap_parse_flags (const gchar *flag_str);
277 static MsgInfo *imap_parse_envelope (SockInfo *sock,
280 static gint imap_greeting (IMAPSession *session);
281 static gboolean imap_has_capability (IMAPSession *session,
283 void imap_free_capabilities (IMAPSession *session);
284 static const IMAPSet numberlist_to_imapset(MsgNumberList *list);
286 /* low-level IMAP4rev1 commands */
287 static gint imap_cmd_login (IMAPSession *sock,
290 static gint imap_cmd_logout (IMAPSession *sock);
291 static gint imap_cmd_noop (IMAPSession *sock);
292 static gint imap_cmd_starttls (IMAPSession *sock);
293 static gint imap_cmd_namespace (IMAPSession *sock,
295 static gint imap_cmd_list (IMAPSession *session,
297 const gchar *mailbox,
299 static gint imap_cmd_do_select (IMAPSession *sock,
305 guint32 *uid_validity);
306 static gint imap_cmd_select (IMAPSession *sock,
311 guint32 *uid_validity);
312 static gint imap_cmd_examine (IMAPSession *sock,
317 guint32 *uid_validity);
318 static gint imap_cmd_create (IMAPSession *sock,
319 const gchar *folder);
320 static gint imap_cmd_rename (IMAPSession *sock,
321 const gchar *oldfolder,
322 const gchar *newfolder);
323 static gint imap_cmd_delete (IMAPSession *sock,
324 const gchar *folder);
325 static gint imap_cmd_envelope (IMAPSession *sock,
327 static gint imap_cmd_fetch (IMAPSession *sock,
329 const gchar *filename);
330 static gint imap_cmd_append (IMAPSession *session,
331 const gchar *destfolder,
334 static gint imap_cmd_copy (IMAPSession *session,
336 const gchar *destfolder,
338 static gint imap_cmd_store (IMAPSession *sock,
341 static gint imap_cmd_expunge (IMAPSession *sock);
343 static gint imap_cmd_ok (IMAPSession *session,
345 static void imap_gen_send (IMAPSession *sock,
346 const gchar *format, ...);
347 static gint imap_gen_recv (IMAPSession *sock,
350 /* misc utility functions */
351 static gchar *strchr_cpy (const gchar *src,
355 static gchar *get_quoted (const gchar *src,
359 static gchar *search_array_contain_str (GPtrArray *array,
361 static gchar *search_array_str (GPtrArray *array,
363 static void imap_path_separator_subst (gchar *str,
366 static gchar *imap_modified_utf7_to_locale (const gchar *mutf7_str);
367 static gchar *imap_locale_to_modified_utf7 (const gchar *from);
369 static gboolean imap_rename_folder_func (GNode *node,
371 static gint imap_get_num_list (Folder *folder,
374 static GSList *imap_get_msginfos (Folder *folder,
376 GSList *msgnum_list);
377 static MsgInfo *imap_get_msginfo (Folder *folder,
380 static gboolean imap_check_msgnum_validity (Folder *folder,
382 static void imap_change_flags (Folder *folder,
385 MsgPermFlags newflags);
387 FolderClass imap_class =
393 /* Folder functions */
399 /* FolderItem functions */
400 imap_folder_item_new,
401 imap_folder_item_destroy,
409 imap_check_msgnum_validity,
411 /* Message functions */
423 FolderClass *imap_get_class(void)
428 Folder *imap_folder_new(const gchar *name, const gchar *path)
432 folder = (Folder *)g_new0(IMAPFolder, 1);
433 folder->klass = &imap_class;
434 imap_folder_init(folder, name, path);
439 void imap_folder_destroy(Folder *folder)
443 dir = folder_get_path(folder);
444 if (is_dir_exist(dir))
445 remove_dir_recursive(dir);
448 folder_remote_folder_destroy(REMOTE_FOLDER(folder));
451 static void imap_folder_init(Folder *folder, const gchar *name,
454 folder_remote_folder_init((Folder *)folder, name, path);
457 static FolderItem *imap_folder_item_new(Folder *folder)
459 IMAPFolderItem *item;
461 item = g_new0(IMAPFolderItem, 1);
464 item->uid_list = NULL;
466 return (FolderItem *)item;
469 static void imap_folder_item_destroy(Folder *folder, FolderItem *_item)
471 IMAPFolderItem *item = (IMAPFolderItem *)_item;
473 g_return_if_fail(item != NULL);
474 g_slist_free(item->uid_list);
479 static gboolean imap_reset_uid_lists_func(GNode *node, gpointer data)
481 IMAPFolderItem *item = (IMAPFolderItem *)node->data;
485 g_slist_free(item->uid_list);
486 item->uid_list = NULL;
491 static void imap_reset_uid_lists(Folder *folder)
493 if(folder->node == NULL)
496 /* Destroy all uid lists and rest last uid */
497 g_node_traverse(folder->node, G_IN_ORDER, G_TRAVERSE_ALL, -1, imap_reset_uid_lists_func, NULL);
500 static IMAPSession *imap_session_get(Folder *folder)
502 RemoteFolder *rfolder = REMOTE_FOLDER(folder);
503 IMAPSession *session = NULL;
506 g_return_val_if_fail(folder != NULL, NULL);
507 g_return_val_if_fail(FOLDER_CLASS(folder) == &imap_class, NULL);
508 g_return_val_if_fail(folder->account != NULL, NULL);
511 port = folder->account->set_imapport ? folder->account->imapport
512 : folder->account->ssl_imap == SSL_TUNNEL
513 ? IMAPS_PORT : IMAP4_PORT;
515 port = folder->account->set_imapport ? folder->account->imapport
519 /* Make sure we have a session */
520 if (rfolder->session != NULL) {
521 session = IMAP_SESSION(rfolder->session);
523 imap_reset_uid_lists(folder);
524 session = imap_session_new(folder->account);
529 if (SESSION(session)->sock->state == CONN_DISCONNECTED) {
530 debug_print("IMAP server disconnected\n");
531 session_destroy(SESSION(session));
532 imap_reset_uid_lists(folder);
533 session = imap_session_new(folder->account);
536 /* Make sure session is authenticated */
537 if (!IMAP_SESSION(session)->authenticated)
538 imap_session_authenticate(IMAP_SESSION(session), folder->account);
539 if (!IMAP_SESSION(session)->authenticated) {
540 session_destroy(SESSION(session));
541 rfolder->session = NULL;
545 /* Make sure we have parsed the IMAP namespace */
546 imap_parse_namespace(IMAP_SESSION(session),
547 IMAP_FOLDER(folder));
549 /* I think the point of this code is to avoid sending a
550 * keepalive if we've used the session recently and therefore
551 * think it's still alive. Unfortunately, most of the code
552 * does not yet check for errors on the socket, and so if the
553 * connection drops we don't notice until the timeout expires.
554 * A better solution than sending a NOOP every time would be
555 * for every command to be prepared to retry until it is
556 * successfully sent. -- mbp */
557 if (time(NULL) - session->last_access_time > SESSION_TIMEOUT) {
558 /* verify that the session is still alive */
559 if (imap_cmd_noop(session) != IMAP_SUCCESS) {
560 /* Check if this is the first try to establish a
561 connection, if yes we don't try to reconnect */
562 if (rfolder->session == NULL) {
563 log_warning(_("Connecting %s:%d failed"),
564 folder->account->recv_server, port);
565 session_destroy(SESSION(session));
568 log_warning(_("IMAP4 connection to %s:%d has been"
569 " disconnected. Reconnecting...\n"),
570 folder->account->recv_server, port);
571 session_destroy(SESSION(session));
572 /* Clear folders session to make imap_session_get create
573 a new session, because of rfolder->session == NULL
574 it will not try to reconnect again and so avoid an
576 rfolder->session = NULL;
577 session = imap_session_get(folder);
582 rfolder->session = SESSION(session);
584 session->last_access_time = time(NULL);
586 return IMAP_SESSION(session);
589 IMAPSession *imap_session_new(const PrefsAccount *account)
591 IMAPSession *session;
597 /* FIXME: IMAP over SSL only... */
600 port = account->set_imapport ? account->imapport
601 : account->ssl_imap == SSL_TUNNEL ? IMAPS_PORT : IMAP4_PORT;
602 ssl_type = account->ssl_imap;
604 port = account->set_imapport ? account->imapport
608 if (account->set_tunnelcmd) {
609 log_message(_("creating tunneled IMAP4 connection\n"));
611 if ((imap_sock = imap_open_tunnel(account->recv_server,
615 if ((imap_sock = imap_open_tunnel(account->recv_server,
616 account->tunnelcmd)) == NULL)
620 g_return_val_if_fail(account->recv_server != NULL, NULL);
622 log_message(_("creating IMAP4 connection to %s:%d ...\n"),
623 account->recv_server, port);
626 if ((imap_sock = imap_open(account->recv_server, port,
629 if ((imap_sock = imap_open(account->recv_server, port)) == NULL)
634 session = g_new0(IMAPSession, 1);
635 session_init(SESSION(session));
636 SESSION(session)->type = SESSION_IMAP;
637 SESSION(session)->server = g_strdup(account->recv_server);
638 SESSION(session)->sock = imap_sock;
640 SESSION(session)->destroy = imap_session_destroy;
642 session->capability = NULL;
644 session->mbox = NULL;
645 session->authenticated = is_preauth;
646 session->cmd_count = 0;
648 /* Only need to log in if the connection was not PREAUTH */
649 if (imap_greeting(session) != IMAP_SUCCESS) {
650 session_destroy(SESSION(session));
655 if (account->ssl_imap == SSL_STARTTLS && imap_has_capability(session, "STARTTLS")) {
658 ok = imap_cmd_starttls(session);
659 if (ok != IMAP_SUCCESS) {
660 log_warning(_("Can't start TLS session.\n"));
661 session_destroy(SESSION(session));
664 if (!ssl_init_socket_with_method(SESSION(session)->sock, SSL_METHOD_TLSv1)) {
665 session_destroy(SESSION(session));
669 imap_free_capabilities(session);
670 session->authenticated = is_preauth;
671 session->cmd_count = 1;
673 if (imap_greeting(session) != IMAP_SUCCESS) {
674 session_destroy(SESSION(session));
679 log_message("IMAP connection is %s-authenticated\n",
680 (is_preauth) ? "pre" : "un");
685 void imap_session_authenticate(IMAPSession *session, const PrefsAccount *account)
689 g_return_if_fail(account->userid != NULL);
691 pass = account->passwd;
694 tmp_pass = input_dialog_query_password(account->recv_server, account->userid);
697 Xstrdup_a(pass, tmp_pass, {g_free(tmp_pass); return;});
701 if (imap_cmd_login(session, account->userid, pass) != IMAP_SUCCESS) {
702 imap_cmd_logout(session);
706 session->authenticated = TRUE;
709 void imap_session_destroy(Session *session)
711 sock_close(session->sock);
712 session->sock = NULL;
714 g_free(IMAP_SESSION(session)->mbox);
715 imap_free_capabilities(IMAP_SESSION(session));
718 gchar *imap_fetch_msg(Folder *folder, FolderItem *item, gint uid)
720 gchar *path, *filename;
721 IMAPSession *session;
724 g_return_val_if_fail(folder != NULL, NULL);
725 g_return_val_if_fail(item != NULL, NULL);
727 path = folder_item_get_path(item);
728 if (!is_dir_exist(path))
730 filename = g_strconcat(path, G_DIR_SEPARATOR_S, itos(uid), NULL);
733 if (is_file_exist(filename)) {
734 debug_print("message %d has been already cached.\n", uid);
738 session = imap_session_get(folder);
744 ok = imap_select(session, IMAP_FOLDER(folder), item->path,
745 NULL, NULL, NULL, NULL);
746 if (ok != IMAP_SUCCESS) {
747 g_warning("can't select mailbox %s\n", item->path);
752 debug_print("getting message %d...\n", uid);
753 ok = imap_cmd_fetch(session, (guint32)uid, filename);
755 if (ok != IMAP_SUCCESS) {
756 g_warning("can't fetch message %d\n", uid);
764 gint imap_add_msg(Folder *folder, FolderItem *dest, const gchar *file,
765 gboolean remove_source)
768 IMAPSession *session;
771 g_return_val_if_fail(folder != NULL, -1);
772 g_return_val_if_fail(dest != NULL, -1);
773 g_return_val_if_fail(file != NULL, -1);
775 session = imap_session_get(folder);
776 if (!session) return -1;
778 destdir = imap_get_real_path(IMAP_FOLDER(folder), dest->path);
779 ok = imap_cmd_append(session, destdir, file, &newuid);
782 if (ok != IMAP_SUCCESS) {
783 g_warning("can't append message %s\n", file);
788 if (unlink(file) < 0)
789 FILE_OP_ERROR(file, "unlink");
795 static gint imap_do_copy(Folder *folder, FolderItem *dest, MsgInfo *msginfo,
796 gboolean remove_source)
799 IMAPSession *session;
803 g_return_val_if_fail(folder != NULL, -1);
804 g_return_val_if_fail(FOLDER_CLASS(folder) == &imap_class, -1);
805 g_return_val_if_fail(dest != NULL, -1);
806 g_return_val_if_fail(msginfo != NULL, -1);
808 session = imap_session_get(folder);
809 if (!session) return -1;
811 if (msginfo->folder == dest) {
812 g_warning("the src folder is identical to the dest.\n");
816 destdir = imap_get_real_path(IMAP_FOLDER(folder), dest->path);
818 /* ensure source folder selected */
819 ok = imap_select(session, IMAP_FOLDER(folder), msginfo->folder->path,
820 NULL, NULL, NULL, NULL);
821 if (ok != IMAP_SUCCESS)
825 debug_print("Moving message %s%c%d to %s ...\n",
826 msginfo->folder->path, G_DIR_SEPARATOR,
827 msginfo->msgnum, destdir);
829 debug_print("Copying message %s%c%d to %s ...\n",
830 msginfo->folder->path, G_DIR_SEPARATOR,
831 msginfo->msgnum, destdir);
833 ok = imap_cmd_copy(session, msginfo->msgnum, destdir, &newuid);
835 if (ok == IMAP_SUCCESS && remove_source) {
836 MsgNumberList numlist;
839 numlist.data = GINT_TO_POINTER(msginfo->msgnum);
841 imap_set_message_flags(session, &numlist,
842 IMAP_FLAG_DELETED, TRUE);
843 ok = imap_cmd_expunge(session);
848 if (ok == IMAP_SUCCESS)
854 gint imap_copy_msg(Folder *folder, FolderItem *dest, MsgInfo *msginfo)
859 g_return_val_if_fail(folder != NULL, -1);
860 g_return_val_if_fail(dest != NULL, -1);
861 g_return_val_if_fail(msginfo != NULL, -1);
862 g_return_val_if_fail(msginfo->folder != NULL, -1);
864 if (folder == msginfo->folder->folder)
865 return imap_do_copy(folder, dest, msginfo, FALSE);
867 srcfile = procmsg_get_message_file(msginfo);
868 if (!srcfile) return -1;
870 ret = imap_add_msg(folder, dest, srcfile, FALSE);
877 gint imap_remove_msg(Folder *folder, FolderItem *item, gint uid)
880 IMAPSession *session;
882 MsgNumberList numlist;
884 g_return_val_if_fail(folder != NULL, -1);
885 g_return_val_if_fail(FOLDER_CLASS(folder) == &imap_class, -1);
886 g_return_val_if_fail(item != NULL, -1);
888 session = imap_session_get(folder);
889 if (!session) return -1;
891 ok = imap_select(session, IMAP_FOLDER(folder), item->path,
892 NULL, NULL, NULL, NULL);
893 if (ok != IMAP_SUCCESS)
897 numlist.data = GINT_TO_POINTER(uid);
899 ok = imap_set_message_flags
900 (IMAP_SESSION(REMOTE_FOLDER(folder)->session),
901 &numlist, IMAP_FLAG_DELETED, TRUE);
902 if (ok != IMAP_SUCCESS) {
903 log_warning(_("can't set deleted flags: %d\n"), uid);
907 ok = imap_cmd_expunge(session);
908 if (ok != IMAP_SUCCESS) {
909 log_warning(_("can't expunge\n"));
913 dir = folder_item_get_path(item);
914 if (is_dir_exist(dir))
915 remove_numbered_files(dir, uid, uid);
921 gint imap_remove_all_msg(Folder *folder, FolderItem *item)
923 gint exists, recent, unseen;
924 guint32 uid_validity;
926 IMAPSession *session;
929 g_return_val_if_fail(folder != NULL, -1);
930 g_return_val_if_fail(item != NULL, -1);
932 session = imap_session_get(folder);
933 if (!session) return -1;
935 ok = imap_select(session, IMAP_FOLDER(folder), item->path,
936 &exists, &recent, &unseen, &uid_validity);
937 if (ok != IMAP_SUCCESS)
942 imap_gen_send(session,
943 "STORE 1:%d +FLAGS (\\Deleted)", exists);
944 ok = imap_cmd_ok(session, NULL);
945 if (ok != IMAP_SUCCESS) {
946 log_warning(_("can't set deleted flags: 1:%d\n"), exists);
950 ok = imap_cmd_expunge(session);
951 if (ok != IMAP_SUCCESS) {
952 log_warning(_("can't expunge\n"));
956 dir = folder_item_get_path(item);
957 if (is_dir_exist(dir))
958 remove_all_numbered_files(dir);
964 gboolean imap_is_msg_changed(Folder *folder, FolderItem *item, MsgInfo *msginfo)
966 /* TODO: properly implement this method */
970 void imap_scan_tree(Folder *folder)
973 IMAPSession *session;
974 gchar *root_folder = NULL;
976 g_return_if_fail(folder != NULL);
977 g_return_if_fail(folder->account != NULL);
979 session = imap_session_get(folder);
982 folder_tree_destroy(folder);
983 item = folder_item_new(folder, folder->name, NULL);
984 item->folder = folder;
985 folder->node = g_node_new(item);
990 if (folder->account->imap_dir && *folder->account->imap_dir) {
991 Xstrdup_a(root_folder, folder->account->imap_dir, return);
992 strtailchomp(root_folder, '/');
993 debug_print("IMAP root directory: %s\n", root_folder);
996 item = folder_item_new(folder, folder->name, root_folder);
997 item->folder = folder;
998 item->no_select = TRUE;
999 folder->node = g_node_new(item);
1001 imap_scan_tree_recursive(session, item);
1003 imap_create_missing_folders(folder);
1006 static gint imap_scan_tree_recursive(IMAPSession *session, FolderItem *item)
1009 IMAPFolder *imapfolder;
1010 FolderItem *new_item;
1011 GSList *item_list, *cur;
1013 gchar *wildcard_path;
1017 g_return_val_if_fail(item != NULL, -1);
1018 g_return_val_if_fail(item->folder != NULL, -1);
1019 g_return_val_if_fail(item->no_sub == FALSE, -1);
1021 folder = FOLDER(item->folder);
1022 imapfolder = IMAP_FOLDER(folder);
1024 separator = imap_get_path_separator(imapfolder, item->path);
1026 if (item->folder->ui_func)
1027 item->folder->ui_func(folder, item, folder->ui_func_data);
1030 wildcard[0] = separator;
1033 real_path = imap_get_real_path(imapfolder, item->path);
1037 real_path = g_strdup("");
1040 Xstrcat_a(wildcard_path, real_path, wildcard,
1041 {g_free(real_path); return IMAP_ERROR;});
1042 QUOTE_IF_REQUIRED(wildcard_path, wildcard_path);
1044 imap_gen_send(session, "LIST \"\" %s",
1047 strtailchomp(real_path, separator);
1048 item_list = imap_parse_list(folder, session, real_path, NULL);
1051 for (cur = item_list; cur != NULL; cur = cur->next) {
1052 new_item = cur->data;
1053 if (!strcmp(new_item->path, "INBOX")) {
1054 if (!folder->inbox) {
1055 new_item->stype = F_INBOX;
1056 item->folder->inbox = new_item;
1058 folder_item_destroy(new_item);
1061 } else if (!item->parent || item->stype == F_INBOX) {
1064 base = g_basename(new_item->path);
1066 if (!folder->outbox && !strcasecmp(base, "Sent")) {
1067 new_item->stype = F_OUTBOX;
1068 folder->outbox = new_item;
1069 } else if (!folder->draft && !strcasecmp(base, "Drafts")) {
1070 new_item->stype = F_DRAFT;
1071 folder->draft = new_item;
1072 } else if (!folder->queue && !strcasecmp(base, "Queue")) {
1073 new_item->stype = F_QUEUE;
1074 folder->queue = new_item;
1075 } else if (!folder->trash && !strcasecmp(base, "Trash")) {
1076 new_item->stype = F_TRASH;
1077 folder->trash = new_item;
1080 folder_item_append(item, new_item);
1081 if (new_item->no_sub == FALSE)
1082 imap_scan_tree_recursive(session, new_item);
1085 return IMAP_SUCCESS;
1088 static GSList *imap_parse_list(Folder *folder, IMAPSession *session,
1089 const gchar *real_path, gchar *separator)
1091 gchar buf[IMAPBUFSIZE];
1093 gchar separator_str[16];
1096 gchar *loc_name, *loc_path;
1097 GSList *item_list = NULL;
1099 FolderItem *new_item;
1101 debug_print("getting list of %s ...\n",
1102 *real_path ? real_path : "\"\"");
1104 str = g_string_new(NULL);
1107 if (sock_gets(SESSION(session)->sock, buf, sizeof(buf)) <= 0) {
1108 log_warning(_("error occurred while getting LIST.\n"));
1112 if (buf[0] != '*' || buf[1] != ' ') {
1113 log_print("IMAP4< %s\n", buf);
1116 debug_print("IMAP4< %s\n", buf);
1118 g_string_assign(str, buf);
1120 if (strncmp(p, "LIST ", 5) != 0) continue;
1123 if (*p != '(') continue;
1125 p = strchr_cpy(p, ')', flags, sizeof(flags));
1127 while (*p == ' ') p++;
1129 p = strchr_cpy(p, ' ', separator_str, sizeof(separator_str));
1131 extract_quote(separator_str, '"');
1132 if (!strcmp(separator_str, "NIL"))
1133 separator_str[0] = '\0';
1135 *separator = separator_str[0];
1138 while (*p == ' ') p++;
1139 if (*p == '{' || *p == '"')
1140 p = imap_parse_atom(SESSION(session)->sock, p,
1141 buf, sizeof(buf), str);
1143 strncpy2(buf, p, sizeof(buf));
1144 strtailchomp(buf, separator_str[0]);
1145 if (buf[0] == '\0') continue;
1146 if (!strcmp(buf, real_path)) continue;
1148 if (separator_str[0] != '\0')
1149 subst_char(buf, separator_str[0], '/');
1150 name = g_basename(buf);
1151 if (name[0] == '.') continue;
1153 loc_name = imap_modified_utf7_to_locale(name);
1154 loc_path = imap_modified_utf7_to_locale(buf);
1155 new_item = folder_item_new(folder, loc_name, loc_path);
1156 if (strcasestr(flags, "\\Noinferiors") != NULL)
1157 new_item->no_sub = TRUE;
1158 if (strcmp(buf, "INBOX") != 0 &&
1159 strcasestr(flags, "\\Noselect") != NULL)
1160 new_item->no_select = TRUE;
1162 item_list = g_slist_append(item_list, new_item);
1164 debug_print("folder %s has been added.\n", loc_path);
1169 g_string_free(str, TRUE);
1174 gint imap_create_tree(Folder *folder)
1176 g_return_val_if_fail(folder != NULL, -1);
1177 g_return_val_if_fail(folder->node != NULL, -1);
1178 g_return_val_if_fail(folder->node->data != NULL, -1);
1179 g_return_val_if_fail(folder->account != NULL, -1);
1181 imap_scan_tree(folder);
1182 imap_create_missing_folders(folder);
1187 static void imap_create_missing_folders(Folder *folder)
1189 g_return_if_fail(folder != NULL);
1192 folder->inbox = imap_create_special_folder
1193 (folder, F_INBOX, "INBOX");
1195 if (!folder->outbox)
1196 folder->outbox = imap_create_special_folder
1197 (folder, F_OUTBOX, "Sent");
1199 folder->draft = imap_create_special_folder
1200 (folder, F_DRAFT, "Drafts");
1202 folder->queue = imap_create_special_folder
1203 (folder, F_QUEUE, "Queue");
1206 folder->trash = imap_create_special_folder
1207 (folder, F_TRASH, "Trash");
1210 static FolderItem *imap_create_special_folder(Folder *folder,
1211 SpecialFolderItemType stype,
1215 FolderItem *new_item;
1217 g_return_val_if_fail(folder != NULL, NULL);
1218 g_return_val_if_fail(folder->node != NULL, NULL);
1219 g_return_val_if_fail(folder->node->data != NULL, NULL);
1220 g_return_val_if_fail(folder->account != NULL, NULL);
1221 g_return_val_if_fail(name != NULL, NULL);
1223 item = FOLDER_ITEM(folder->node->data);
1224 new_item = imap_create_folder(folder, item, name);
1227 g_warning("Can't create '%s'\n", name);
1228 if (!folder->inbox) return NULL;
1230 new_item = imap_create_folder(folder, folder->inbox, name);
1232 g_warning("Can't create '%s' under INBOX\n", name);
1234 new_item->stype = stype;
1236 new_item->stype = stype;
1241 FolderItem *imap_create_folder(Folder *folder, FolderItem *parent,
1244 gchar *dirpath, *imap_path;
1245 IMAPSession *session;
1246 FolderItem *new_item;
1252 g_return_val_if_fail(folder != NULL, NULL);
1253 g_return_val_if_fail(folder->account != NULL, NULL);
1254 g_return_val_if_fail(parent != NULL, NULL);
1255 g_return_val_if_fail(name != NULL, NULL);
1257 session = imap_session_get(folder);
1258 if (!session) return NULL;
1260 if (!parent->parent && strcmp(name, "INBOX") == 0)
1261 dirpath = g_strdup(name);
1262 else if (parent->path)
1263 dirpath = g_strconcat(parent->path, "/", name, NULL);
1264 else if ((p = strchr(name, '/')) != NULL && *(p + 1) != '\0')
1265 dirpath = g_strdup(name);
1266 else if (folder->account->imap_dir && *folder->account->imap_dir) {
1269 Xstrdup_a(imap_dir, folder->account->imap_dir, return NULL);
1270 strtailchomp(imap_dir, '/');
1271 dirpath = g_strconcat(imap_dir, "/", name, NULL);
1273 dirpath = g_strdup(name);
1275 /* keep trailing directory separator to create a folder that contains
1277 imap_path = imap_locale_to_modified_utf7(dirpath);
1278 strtailchomp(dirpath, '/');
1279 Xstrdup_a(new_name, name, {g_free(dirpath); return NULL;});
1280 strtailchomp(new_name, '/');
1281 separator = imap_get_path_separator(IMAP_FOLDER(folder), imap_path);
1282 imap_path_separator_subst(imap_path, separator);
1283 subst_char(new_name, '/', separator);
1285 if (strcmp(name, "INBOX") != 0) {
1288 gboolean exist = FALSE;
1290 argbuf = g_ptr_array_new();
1291 ok = imap_cmd_list(session, NULL, imap_path,
1293 if (ok != IMAP_SUCCESS) {
1294 log_warning(_("can't create mailbox: LIST failed\n"));
1297 ptr_array_free_strings(argbuf);
1298 g_ptr_array_free(argbuf, TRUE);
1302 for (i = 0; i < argbuf->len; i++) {
1304 str = g_ptr_array_index(argbuf, i);
1305 if (!strncmp(str, "LIST ", 5)) {
1310 ptr_array_free_strings(argbuf);
1311 g_ptr_array_free(argbuf, TRUE);
1314 ok = imap_cmd_create(session, imap_path);
1315 if (ok != IMAP_SUCCESS) {
1316 log_warning(_("can't create mailbox\n"));
1324 new_item = folder_item_new(folder, new_name, dirpath);
1325 folder_item_append(parent, new_item);
1329 dirpath = folder_item_get_path(new_item);
1330 if (!is_dir_exist(dirpath))
1331 make_dir_hier(dirpath);
1337 gint imap_rename_folder(Folder *folder, FolderItem *item, const gchar *name)
1341 gchar *real_oldpath;
1342 gchar *real_newpath;
1345 gchar *old_cache_dir;
1346 gchar *new_cache_dir;
1347 IMAPSession *session;
1350 gint exists, recent, unseen;
1351 guint32 uid_validity;
1353 g_return_val_if_fail(folder != NULL, -1);
1354 g_return_val_if_fail(item != NULL, -1);
1355 g_return_val_if_fail(item->path != NULL, -1);
1356 g_return_val_if_fail(name != NULL, -1);
1358 session = imap_session_get(folder);
1359 if (!session) return -1;
1361 real_oldpath = imap_get_real_path(IMAP_FOLDER(folder), item->path);
1363 g_free(session->mbox);
1364 session->mbox = NULL;
1365 ok = imap_cmd_examine(session, "INBOX",
1366 &exists, &recent, &unseen, &uid_validity);
1367 if (ok != IMAP_SUCCESS) {
1368 g_free(real_oldpath);
1372 separator = imap_get_path_separator(IMAP_FOLDER(folder), item->path);
1373 if (strchr(item->path, G_DIR_SEPARATOR)) {
1374 dirpath = g_dirname(item->path);
1375 newpath = g_strconcat(dirpath, G_DIR_SEPARATOR_S, name, NULL);
1378 newpath = g_strdup(name);
1380 real_newpath = imap_locale_to_modified_utf7(newpath);
1381 imap_path_separator_subst(real_newpath, separator);
1383 ok = imap_cmd_rename(session, real_oldpath, real_newpath);
1384 if (ok != IMAP_SUCCESS) {
1385 log_warning(_("can't rename mailbox: %s to %s\n"),
1386 real_oldpath, real_newpath);
1387 g_free(real_oldpath);
1389 g_free(real_newpath);
1394 item->name = g_strdup(name);
1396 old_cache_dir = folder_item_get_path(item);
1398 node = g_node_find(item->folder->node, G_PRE_ORDER, G_TRAVERSE_ALL,
1400 paths[0] = g_strdup(item->path);
1402 g_node_traverse(node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
1403 imap_rename_folder_func, paths);
1405 if (is_dir_exist(old_cache_dir)) {
1406 new_cache_dir = folder_item_get_path(item);
1407 if (rename(old_cache_dir, new_cache_dir) < 0) {
1408 FILE_OP_ERROR(old_cache_dir, "rename");
1410 g_free(new_cache_dir);
1413 g_free(old_cache_dir);
1416 g_free(real_oldpath);
1417 g_free(real_newpath);
1422 gint imap_remove_folder(Folder *folder, FolderItem *item)
1425 IMAPSession *session;
1428 gint exists, recent, unseen;
1429 guint32 uid_validity;
1431 g_return_val_if_fail(folder != NULL, -1);
1432 g_return_val_if_fail(item != NULL, -1);
1433 g_return_val_if_fail(item->path != NULL, -1);
1435 session = imap_session_get(folder);
1436 if (!session) return -1;
1438 path = imap_get_real_path(IMAP_FOLDER(folder), item->path);
1440 ok = imap_cmd_examine(session, "INBOX",
1441 &exists, &recent, &unseen, &uid_validity);
1442 if (ok != IMAP_SUCCESS) {
1447 ok = imap_cmd_delete(session, path);
1448 if (ok != IMAP_SUCCESS) {
1449 log_warning(_("can't delete mailbox\n"));
1455 cache_dir = folder_item_get_path(item);
1456 if (is_dir_exist(cache_dir) && remove_dir_recursive(cache_dir) < 0)
1457 g_warning("can't remove directory '%s'\n", cache_dir);
1459 folder_item_remove(item);
1464 static GSList *imap_get_uncached_messages(IMAPSession *session,
1466 MsgNumberList *numlist)
1469 GSList *newlist = NULL;
1470 GSList *llast = NULL;
1474 g_return_val_if_fail(session != NULL, NULL);
1475 g_return_val_if_fail(item != NULL, NULL);
1476 g_return_val_if_fail(item->folder != NULL, NULL);
1477 g_return_val_if_fail(FOLDER_CLASS(item->folder) == &imap_class, NULL);
1479 if (imap_cmd_envelope(session, numberlist_to_imapset(numlist))
1481 log_warning(_("can't get envelope\n"));
1485 str = g_string_new(NULL);
1488 if ((tmp = sock_getline(SESSION(session)->sock)) == NULL) {
1489 log_warning(_("error occurred while getting envelope.\n"));
1490 g_string_free(str, TRUE);
1494 if (tmp[0] != '*' || tmp[1] != ' ') {
1495 log_print("IMAP4< %s\n", tmp);
1499 if (strstr(tmp, "FETCH") == NULL) {
1500 log_print("IMAP4< %s\n", tmp);
1504 log_print("IMAP4< %s\n", tmp);
1505 g_string_assign(str, tmp);
1508 msginfo = imap_parse_envelope
1509 (SESSION(session)->sock, item, str);
1511 log_warning(_("can't parse envelope: %s\n"), str->str);
1514 if (item->stype == F_QUEUE) {
1515 MSG_SET_TMP_FLAGS(msginfo->flags, MSG_QUEUED);
1516 } else if (item->stype == F_DRAFT) {
1517 MSG_SET_TMP_FLAGS(msginfo->flags, MSG_DRAFT);
1520 msginfo->folder = item;
1523 llast = newlist = g_slist_append(newlist, msginfo);
1525 llast = g_slist_append(llast, msginfo);
1526 llast = llast->next;
1530 g_string_free(str, TRUE);
1535 static void imap_delete_all_cached_messages(FolderItem *item)
1539 g_return_if_fail(item != NULL);
1540 g_return_if_fail(item->folder != NULL);
1541 g_return_if_fail(FOLDER_CLASS(item->folder) == &imap_class);
1543 debug_print("Deleting all cached messages...\n");
1545 dir = folder_item_get_path(item);
1546 if (is_dir_exist(dir))
1547 remove_all_numbered_files(dir);
1550 debug_print("done.\n");
1554 static SockInfo *imap_open_tunnel(const gchar *server,
1555 const gchar *tunnelcmd,
1558 static SockInfo *imap_open_tunnel(const gchar *server,
1559 const gchar *tunnelcmd)
1564 if ((sock = sock_connect_cmd(server, tunnelcmd)) == NULL) {
1565 log_warning(_("Can't establish IMAP4 session with: %s\n"),
1570 return imap_init_sock(sock, ssl_type);
1572 return imap_init_sock(sock);
1578 static SockInfo *imap_open(const gchar *server, gushort port,
1581 static SockInfo *imap_open(const gchar *server, gushort port)
1586 if ((sock = sock_connect(server, port)) == NULL) {
1587 log_warning(_("Can't connect to IMAP4 server: %s:%d\n"),
1593 if (ssl_type == SSL_TUNNEL && !ssl_init_socket(sock)) {
1594 log_warning(_("Can't establish IMAP4 session with: %s:%d\n"),
1604 static SockInfo *imap_init_sock(SockInfo *sock, SSLType ssl_type)
1606 static SockInfo *imap_init_sock(SockInfo *sock)
1613 static GList *imap_parse_namespace_str(gchar *str)
1618 IMAPNameSpace *namespace;
1619 GList *ns_list = NULL;
1621 while (*p != '\0') {
1622 /* parse ("#foo" "/") */
1624 while (*p && *p != '(') p++;
1625 if (*p == '\0') break;
1628 while (*p && *p != '"') p++;
1629 if (*p == '\0') break;
1633 while (*p && *p != '"') p++;
1634 if (*p == '\0') break;
1638 while (*p && isspace(*p)) p++;
1639 if (*p == '\0') break;
1640 if (strncmp(p, "NIL", 3) == 0)
1642 else if (*p == '"') {
1645 while (*p && *p != '"') p++;
1646 if (*p == '\0') break;
1651 while (*p && *p != ')') p++;
1652 if (*p == '\0') break;
1655 namespace = g_new(IMAPNameSpace, 1);
1656 namespace->name = g_strdup(name);
1657 namespace->separator = separator ? separator[0] : '\0';
1658 ns_list = g_list_append(ns_list, namespace);
1664 static void imap_parse_namespace(IMAPSession *session, IMAPFolder *folder)
1669 g_return_if_fail(session != NULL);
1670 g_return_if_fail(folder != NULL);
1672 if (folder->ns_personal != NULL ||
1673 folder->ns_others != NULL ||
1674 folder->ns_shared != NULL)
1677 if (!imap_has_capability(session, "NAMESPACE")) {
1678 imap_get_namespace_by_list(session, folder);
1682 if (imap_cmd_namespace(session, &ns_str)
1684 log_warning(_("can't get namespace\n"));
1688 str_array = strsplit_parenthesis(ns_str, '(', ')', 3);
1689 if (str_array == NULL) {
1691 imap_get_namespace_by_list(session, folder);
1695 folder->ns_personal = imap_parse_namespace_str(str_array[0]);
1696 if (str_array[0] && str_array[1])
1697 folder->ns_others = imap_parse_namespace_str(str_array[1]);
1698 if (str_array[0] && str_array[1] && str_array[2])
1699 folder->ns_shared = imap_parse_namespace_str(str_array[2]);
1700 g_strfreev(str_array);
1704 static void imap_get_namespace_by_list(IMAPSession *session, IMAPFolder *folder)
1706 GSList *item_list, *cur;
1707 gchar separator = '\0';
1708 IMAPNameSpace *namespace;
1710 g_return_if_fail(session != NULL);
1711 g_return_if_fail(folder != NULL);
1713 if (folder->ns_personal != NULL ||
1714 folder->ns_others != NULL ||
1715 folder->ns_shared != NULL)
1718 imap_gen_send(session, "LIST \"\" \"\"");
1719 item_list = imap_parse_list(NULL, session, "", &separator);
1720 for (cur = item_list; cur != NULL; cur = cur->next)
1721 folder_item_destroy(FOLDER_ITEM(cur->data));
1722 g_slist_free(item_list);
1724 namespace = g_new(IMAPNameSpace, 1);
1725 namespace->name = g_strdup("");
1726 namespace->separator = separator;
1727 folder->ns_personal = g_list_append(NULL, namespace);
1730 static IMAPNameSpace *imap_find_namespace_from_list(GList *ns_list,
1733 IMAPNameSpace *namespace = NULL;
1734 gchar *tmp_path, *name;
1736 if (!path) path = "";
1738 Xstrcat_a(tmp_path, path, "/", return NULL);
1740 for (; ns_list != NULL; ns_list = ns_list->next) {
1741 IMAPNameSpace *tmp_ns = ns_list->data;
1743 Xstrdup_a(name, tmp_ns->name, return namespace);
1744 if (tmp_ns->separator && tmp_ns->separator != '/')
1745 subst_char(name, tmp_ns->separator, '/');
1746 if (strncmp(tmp_path, name, strlen(name)) == 0)
1753 static IMAPNameSpace *imap_find_namespace(IMAPFolder *folder,
1756 IMAPNameSpace *namespace;
1758 g_return_val_if_fail(folder != NULL, NULL);
1760 namespace = imap_find_namespace_from_list(folder->ns_personal, path);
1761 if (namespace) return namespace;
1762 namespace = imap_find_namespace_from_list(folder->ns_others, path);
1763 if (namespace) return namespace;
1764 namespace = imap_find_namespace_from_list(folder->ns_shared, path);
1765 if (namespace) return namespace;
1770 static gchar imap_get_path_separator(IMAPFolder *folder, const gchar *path)
1772 IMAPNameSpace *namespace;
1773 gchar separator = '/';
1775 namespace = imap_find_namespace(folder, path);
1776 if (namespace && namespace->separator)
1777 separator = namespace->separator;
1782 static gchar *imap_get_real_path(IMAPFolder *folder, const gchar *path)
1787 g_return_val_if_fail(folder != NULL, NULL);
1788 g_return_val_if_fail(path != NULL, NULL);
1790 real_path = imap_locale_to_modified_utf7(path);
1791 separator = imap_get_path_separator(folder, path);
1792 imap_path_separator_subst(real_path, separator);
1797 static gchar *imap_parse_atom(SockInfo *sock, gchar *src,
1798 gchar *dest, gint dest_len, GString *str)
1800 gchar *cur_pos = src;
1803 g_return_val_if_fail(str != NULL, cur_pos);
1805 /* read the next line if the current response buffer is empty */
1806 while (isspace(*cur_pos)) cur_pos++;
1807 while (*cur_pos == '\0') {
1808 if ((nextline = sock_getline(sock)) == NULL)
1810 g_string_assign(str, nextline);
1812 strretchomp(nextline);
1813 /* log_print("IMAP4< %s\n", nextline); */
1814 debug_print("IMAP4< %s\n", nextline);
1817 while (isspace(*cur_pos)) cur_pos++;
1820 if (!strncmp(cur_pos, "NIL", 3)) {
1823 } else if (*cur_pos == '\"') {
1826 p = get_quoted(cur_pos, '\"', dest, dest_len);
1827 cur_pos = p ? p : cur_pos + 2;
1828 } else if (*cur_pos == '{') {
1833 cur_pos = strchr_cpy(cur_pos + 1, '}', buf, sizeof(buf));
1835 g_return_val_if_fail(len > 0, cur_pos);
1837 g_string_truncate(str, 0);
1841 if ((nextline = sock_getline(sock)) == NULL)
1843 line_len += strlen(nextline);
1844 g_string_append(str, nextline);
1846 strretchomp(nextline);
1847 /* log_print("IMAP4< %s\n", nextline); */
1848 debug_print("IMAP4< %s\n", nextline);
1850 } while (line_len < len);
1852 memcpy(dest, cur_pos, MIN(len, dest_len - 1));
1853 dest[MIN(len, dest_len - 1)] = '\0';
1860 static gchar *imap_get_header(SockInfo *sock, gchar *cur_pos, gchar **headers,
1870 g_return_val_if_fail(str != NULL, cur_pos);
1872 while (isspace(*cur_pos)) cur_pos++;
1874 g_return_val_if_fail(*cur_pos == '{', cur_pos);
1876 cur_pos = strchr_cpy(cur_pos + 1, '}', buf, sizeof(buf));
1878 g_return_val_if_fail(len > 0, cur_pos);
1880 g_string_truncate(str, 0);
1884 if ((nextline = sock_getline(sock)) == NULL)
1886 block_len += strlen(nextline);
1887 g_string_append(str, nextline);
1889 strretchomp(nextline);
1890 /* debug_print("IMAP4< %s\n", nextline); */
1892 } while (block_len < len);
1894 debug_print("IMAP4< [contents of BODY.PEEK[HEADER.FIELDS (...)]]\n");
1896 *headers = g_strndup(cur_pos, len);
1899 while (isspace(*cur_pos)) cur_pos++;
1900 while (*cur_pos == '\0') {
1901 if ((nextline = sock_getline(sock)) == NULL)
1903 g_string_assign(str, nextline);
1905 strretchomp(nextline);
1906 debug_print("IMAP4< %s\n", nextline);
1909 while (isspace(*cur_pos)) cur_pos++;
1915 static MsgFlags imap_parse_flags(const gchar *flag_str)
1917 const gchar *p = flag_str;
1918 MsgFlags flags = {0, 0};
1920 flags.perm_flags = MSG_UNREAD;
1922 while ((p = strchr(p, '\\')) != NULL) {
1925 if (g_strncasecmp(p, "Recent", 6) == 0 && MSG_IS_UNREAD(flags)) {
1926 MSG_SET_PERM_FLAGS(flags, MSG_NEW);
1927 } else if (g_strncasecmp(p, "Seen", 4) == 0) {
1928 MSG_UNSET_PERM_FLAGS(flags, MSG_NEW|MSG_UNREAD);
1929 } else if (g_strncasecmp(p, "Deleted", 7) == 0) {
1930 MSG_SET_PERM_FLAGS(flags, MSG_DELETED);
1931 } else if (g_strncasecmp(p, "Flagged", 7) == 0) {
1932 MSG_SET_PERM_FLAGS(flags, MSG_MARKED);
1933 } else if (g_strncasecmp(p, "Answered", 8) == 0) {
1934 MSG_SET_PERM_FLAGS(flags, MSG_REPLIED);
1941 static MsgInfo *imap_parse_envelope(SockInfo *sock, FolderItem *item,
1944 gchar buf[IMAPBUFSIZE];
1945 MsgInfo *msginfo = NULL;
1950 MsgFlags flags = {0, 0}, imap_flags = {0, 0};
1952 g_return_val_if_fail(line_str != NULL, NULL);
1953 g_return_val_if_fail(line_str->str[0] == '*' &&
1954 line_str->str[1] == ' ', NULL);
1956 MSG_SET_TMP_FLAGS(flags, MSG_IMAP);
1957 if (item->stype == F_QUEUE) {
1958 MSG_SET_TMP_FLAGS(flags, MSG_QUEUED);
1959 } else if (item->stype == F_DRAFT) {
1960 MSG_SET_TMP_FLAGS(flags, MSG_DRAFT);
1963 cur_pos = line_str->str + 2;
1965 #define PARSE_ONE_ELEMENT(ch) \
1967 cur_pos = strchr_cpy(cur_pos, ch, buf, sizeof(buf)); \
1968 if (cur_pos == NULL) { \
1969 g_warning("cur_pos == NULL\n"); \
1970 procmsg_msginfo_free(msginfo); \
1975 PARSE_ONE_ELEMENT(' ');
1978 PARSE_ONE_ELEMENT(' ');
1979 g_return_val_if_fail(!strcmp(buf, "FETCH"), NULL);
1981 g_return_val_if_fail(*cur_pos == '(', NULL);
1984 while (*cur_pos != '\0' && *cur_pos != ')') {
1985 while (*cur_pos == ' ') cur_pos++;
1987 if (!strncmp(cur_pos, "UID ", 4)) {
1989 uid = strtoul(cur_pos, &cur_pos, 10);
1990 } else if (!strncmp(cur_pos, "FLAGS ", 6)) {
1992 if (*cur_pos != '(') {
1993 g_warning("*cur_pos != '('\n");
1994 procmsg_msginfo_free(msginfo);
1998 PARSE_ONE_ELEMENT(')');
1999 imap_flags = imap_parse_flags(buf);
2000 } else if (!strncmp(cur_pos, "RFC822.SIZE ", 12)) {
2002 size = strtol(cur_pos, &cur_pos, 10);
2003 } else if (!strncmp(cur_pos, "BODY[HEADER.FIELDS ", 19)) {
2007 if (*cur_pos != '(') {
2008 g_warning("*cur_pos != '('\n");
2009 procmsg_msginfo_free(msginfo);
2013 PARSE_ONE_ELEMENT(')');
2014 if (*cur_pos != ']') {
2015 g_warning("*cur_pos != ']'\n");
2016 procmsg_msginfo_free(msginfo);
2021 cur_pos = imap_get_header(sock, cur_pos, &headers,
2023 msginfo = procheader_parse_str(headers, flags, FALSE, FALSE);
2026 g_warning("invalid FETCH response: %s\n", cur_pos);
2032 msginfo->msgnum = uid;
2033 msginfo->size = size;
2034 msginfo->flags.tmp_flags |= imap_flags.tmp_flags;
2035 msginfo->flags.perm_flags = imap_flags.perm_flags;
2041 static gint imap_set_message_flags(IMAPSession *session,
2042 MsgNumberList *numlist,
2049 buf = g_string_new(is_set ? "+FLAGS (" : "-FLAGS (");
2051 if (IMAP_IS_SEEN(flags)) g_string_append(buf, "\\Seen ");
2052 if (IMAP_IS_ANSWERED(flags)) g_string_append(buf, "\\Answered ");
2053 if (IMAP_IS_FLAGGED(flags)) g_string_append(buf, "\\Flagged ");
2054 if (IMAP_IS_DELETED(flags)) g_string_append(buf, "\\Deleted ");
2055 if (IMAP_IS_DRAFT(flags)) g_string_append(buf, "\\Draft");
2057 if (buf->str[buf->len - 1] == ' ')
2058 g_string_truncate(buf, buf->len - 1);
2060 g_string_append_c(buf, ')');
2062 ok = imap_cmd_store(session, numberlist_to_imapset(numlist),
2064 g_string_free(buf, TRUE);
2069 static gint imap_select(IMAPSession *session, IMAPFolder *folder,
2071 gint *exists, gint *recent, gint *unseen,
2072 guint32 *uid_validity)
2076 gint exists_, recent_, unseen_, uid_validity_;
2078 if (!exists || !recent || !unseen || !uid_validity) {
2079 if (session->mbox && strcmp(session->mbox, path) == 0)
2080 return IMAP_SUCCESS;
2084 uid_validity = &uid_validity_;
2087 g_free(session->mbox);
2088 session->mbox = NULL;
2090 real_path = imap_get_real_path(folder, path);
2091 ok = imap_cmd_select(session, real_path,
2092 exists, recent, unseen, uid_validity);
2093 if (ok != IMAP_SUCCESS)
2094 log_warning(_("can't select folder: %s\n"), real_path);
2096 session->mbox = g_strdup(path);
2097 session->folder_content_changed = FALSE;
2104 #define THROW(err) { ok = err; goto catch; }
2106 static gint imap_status(IMAPSession *session, IMAPFolder *folder,
2108 gint *messages, gint *recent,
2109 guint32 *uid_next, guint32 *uid_validity,
2118 *messages = *recent = *uid_next = *uid_validity = *unseen = 0;
2120 argbuf = g_ptr_array_new();
2122 real_path = imap_get_real_path(folder, path);
2123 QUOTE_IF_REQUIRED(real_path_, real_path);
2124 imap_gen_send(session, "STATUS %s "
2125 "(MESSAGES RECENT UIDNEXT UIDVALIDITY UNSEEN)",
2128 ok = imap_cmd_ok(session, argbuf);
2129 if (ok != IMAP_SUCCESS) THROW(ok);
2131 str = search_array_str(argbuf, "STATUS");
2132 if (!str) THROW(IMAP_ERROR);
2134 str = strchr(str, '(');
2135 if (!str) THROW(IMAP_ERROR);
2137 while (*str != '\0' && *str != ')') {
2138 while (*str == ' ') str++;
2140 if (!strncmp(str, "MESSAGES ", 9)) {
2142 *messages = strtol(str, &str, 10);
2143 } else if (!strncmp(str, "RECENT ", 7)) {
2145 *recent = strtol(str, &str, 10);
2146 } else if (!strncmp(str, "UIDNEXT ", 8)) {
2148 *uid_next = strtoul(str, &str, 10);
2149 } else if (!strncmp(str, "UIDVALIDITY ", 12)) {
2151 *uid_validity = strtoul(str, &str, 10);
2152 } else if (!strncmp(str, "UNSEEN ", 7)) {
2154 *unseen = strtol(str, &str, 10);
2156 g_warning("invalid STATUS response: %s\n", str);
2163 ptr_array_free_strings(argbuf);
2164 g_ptr_array_free(argbuf, TRUE);
2172 /* low-level IMAP4rev1 commands */
2174 static gint imap_cmd_login(IMAPSession *session,
2175 const gchar *user, const gchar *pass)
2177 gchar *user_, *pass_;
2180 QUOTE_IF_REQUIRED(user_, user);
2181 QUOTE_IF_REQUIRED(pass_, pass);
2182 imap_gen_send(session, "LOGIN %s %s", user_, pass_);
2184 ok = imap_cmd_ok(session, NULL);
2185 if (ok != IMAP_SUCCESS)
2186 log_warning(_("IMAP4 login failed.\n"));
2191 static gint imap_cmd_logout(IMAPSession *session)
2193 imap_gen_send(session, "LOGOUT");
2194 return imap_cmd_ok(session, NULL);
2197 /* Send CAPABILITY, and examine the server's response to see whether this
2198 * connection is pre-authenticated or not and build a list of CAPABILITIES. */
2199 static gint imap_greeting(IMAPSession *session)
2204 imap_gen_send(session, "CAPABILITY");
2206 argbuf = g_ptr_array_new();
2208 if (imap_cmd_ok(session, argbuf) != IMAP_SUCCESS ||
2209 ((capstr = search_array_str(argbuf, "CAPABILITY ")) == NULL)) {
2210 ptr_array_free_strings(argbuf);
2211 g_ptr_array_free(argbuf, TRUE);
2215 session->authenticated = search_array_str(argbuf, "PREAUTH") != NULL;
2217 capstr += strlen("CAPABILITY ");
2219 IMAP_SESSION(session)->capability = g_strsplit(capstr, " ", 0);
2221 ptr_array_free_strings(argbuf);
2222 g_ptr_array_free(argbuf, TRUE);
2227 static gboolean imap_has_capability(IMAPSession *session, const gchar *cap)
2231 for (p = session->capability; *p != NULL; ++p)
2232 if (g_strcasecmp(*p, cap) == 0)
2238 void imap_free_capabilities(IMAPSession *session)
2240 g_strfreev(session->capability);
2241 session->capability = NULL;
2244 static const IMAPSet numberlist_to_imapset(MsgNumberList *list)
2246 static GString *imapset = NULL;
2247 MsgNumberList *numlist, *elem;
2248 guint first, last, next;
2250 if (imapset == NULL)
2251 imapset = g_string_sized_new(256);
2253 g_string_truncate(imapset, 0);
2255 numlist = g_slist_copy(list);
2256 numlist = g_slist_sort(numlist, g_int_compare);
2258 first = GPOINTER_TO_INT(numlist->data);
2260 for(elem = g_slist_next(numlist); elem != NULL; elem = g_slist_next(elem)) {
2261 next = GPOINTER_TO_INT(elem->data);
2262 if(next != (last + 1)) {
2263 if (imapset->len > 0)
2264 g_string_append(imapset, ",");
2266 g_string_sprintfa(imapset, "%d", first);
2268 g_string_sprintfa(imapset, "%d:%d", first, last);
2274 if (imapset->len > 0)
2275 g_string_append(imapset, ",");
2277 g_string_sprintfa(imapset, "%d", first);
2279 g_string_sprintfa(imapset, "%d:%d", first, last);
2281 g_slist_free(numlist);
2283 return imapset->str;
2286 static gint imap_cmd_noop(IMAPSession *session)
2288 imap_gen_send(session, "NOOP");
2289 return imap_cmd_ok(session, NULL);
2292 static gint imap_cmd_starttls(IMAPSession *session)
2294 imap_gen_send(session, "STARTTLS");
2295 return imap_cmd_ok(session, NULL);
2298 #define THROW(err) { ok = err; goto catch; }
2300 static gint imap_cmd_namespace(IMAPSession *session, gchar **ns_str)
2306 argbuf = g_ptr_array_new();
2308 imap_gen_send(session, "NAMESPACE");
2309 if ((ok = imap_cmd_ok(session, argbuf)) != IMAP_SUCCESS) THROW(ok);
2311 str = search_array_str(argbuf, "NAMESPACE");
2312 if (!str) THROW(IMAP_ERROR);
2314 *ns_str = g_strdup(str);
2317 ptr_array_free_strings(argbuf);
2318 g_ptr_array_free(argbuf, TRUE);
2325 static gint imap_cmd_list(IMAPSession *session, const gchar *ref,
2326 const gchar *mailbox, GPtrArray *argbuf)
2328 gchar *ref_, *mailbox_;
2330 if (!ref) ref = "\"\"";
2331 if (!mailbox) mailbox = "\"\"";
2333 QUOTE_IF_REQUIRED(ref_, ref);
2334 QUOTE_IF_REQUIRED(mailbox_, mailbox);
2335 imap_gen_send(session, "LIST %s %s", ref_, mailbox_);
2337 return imap_cmd_ok(session, argbuf);
2340 #define THROW goto catch
2342 static gint imap_cmd_do_select(IMAPSession *session, const gchar *folder,
2344 gint *exists, gint *recent, gint *unseen,
2345 guint32 *uid_validity)
2353 *exists = *recent = *unseen = *uid_validity = 0;
2354 argbuf = g_ptr_array_new();
2357 select_cmd = "EXAMINE";
2359 select_cmd = "SELECT";
2361 QUOTE_IF_REQUIRED(folder_, folder);
2362 imap_gen_send(session, "%s %s", select_cmd, folder_);
2364 if ((ok = imap_cmd_ok(session, argbuf)) != IMAP_SUCCESS) THROW;
2366 resp_str = search_array_contain_str(argbuf, "EXISTS");
2368 if (sscanf(resp_str,"%d EXISTS", exists) != 1) {
2369 g_warning("imap_cmd_select(): invalid EXISTS line.\n");
2374 resp_str = search_array_contain_str(argbuf, "RECENT");
2376 if (sscanf(resp_str, "%d RECENT", recent) != 1) {
2377 g_warning("imap_cmd_select(): invalid RECENT line.\n");
2382 resp_str = search_array_contain_str(argbuf, "UIDVALIDITY");
2384 if (sscanf(resp_str, "OK [UIDVALIDITY %u] ", uid_validity)
2386 g_warning("imap_cmd_select(): invalid UIDVALIDITY line.\n");
2391 resp_str = search_array_contain_str(argbuf, "UNSEEN");
2393 if (sscanf(resp_str, "OK [UNSEEN %d] ", unseen) != 1) {
2394 g_warning("imap_cmd_select(): invalid UNSEEN line.\n");
2400 ptr_array_free_strings(argbuf);
2401 g_ptr_array_free(argbuf, TRUE);
2406 static gint imap_cmd_select(IMAPSession *session, const gchar *folder,
2407 gint *exists, gint *recent, gint *unseen,
2408 guint32 *uid_validity)
2410 return imap_cmd_do_select(session, folder, FALSE,
2411 exists, recent, unseen, uid_validity);
2414 static gint imap_cmd_examine(IMAPSession *session, const gchar *folder,
2415 gint *exists, gint *recent, gint *unseen,
2416 guint32 *uid_validity)
2418 return imap_cmd_do_select(session, folder, TRUE,
2419 exists, recent, unseen, uid_validity);
2424 static gint imap_cmd_create(IMAPSession *session, const gchar *folder)
2428 QUOTE_IF_REQUIRED(folder_, folder);
2429 imap_gen_send(session, "CREATE %s", folder_);
2431 return imap_cmd_ok(session, NULL);
2434 static gint imap_cmd_rename(IMAPSession *session, const gchar *old_folder,
2435 const gchar *new_folder)
2437 gchar *old_folder_, *new_folder_;
2439 QUOTE_IF_REQUIRED(old_folder_, old_folder);
2440 QUOTE_IF_REQUIRED(new_folder_, new_folder);
2441 imap_gen_send(session, "RENAME %s %s", old_folder_, new_folder_);
2443 return imap_cmd_ok(session, NULL);
2446 static gint imap_cmd_delete(IMAPSession *session, const gchar *folder)
2450 QUOTE_IF_REQUIRED(folder_, folder);
2451 imap_gen_send(session, "DELETE %s", folder_);
2453 return imap_cmd_ok(session, NULL);
2456 static gint imap_cmd_search(IMAPSession *session, const gchar *criteria, GSList **list)
2462 g_return_val_if_fail(criteria != NULL, IMAP_ERROR);
2463 g_return_val_if_fail(list != NULL, IMAP_ERROR);
2467 argbuf = g_ptr_array_new();
2468 imap_gen_send(session, "UID SEARCH %s", criteria);
2470 ok = imap_cmd_ok(session, argbuf);
2471 if (ok != IMAP_SUCCESS) {
2472 ptr_array_free_strings(argbuf);
2473 g_ptr_array_free(argbuf, TRUE);
2477 if ((uidlist = search_array_str(argbuf, "SEARCH ")) != NULL) {
2478 gchar **strlist, **p;
2480 strlist = g_strsplit(uidlist + 7, " ", 0);
2481 for (p = strlist; *p != NULL; ++p) {
2484 if (sscanf(*p, "%d", &msgnum) == 1)
2485 *list = g_slist_append(*list, GINT_TO_POINTER(msgnum));
2487 g_strfreev(strlist);
2489 ptr_array_free_strings(argbuf);
2490 g_ptr_array_free(argbuf, TRUE);
2492 return IMAP_SUCCESS;
2495 static gint imap_cmd_fetch(IMAPSession *session, guint32 uid, const gchar *filename)
2503 g_return_val_if_fail(filename != NULL, IMAP_ERROR);
2505 imap_gen_send(session, "UID FETCH %d BODY.PEEK[]", uid);
2507 while ((ok = imap_gen_recv(session, &buf))
2509 if (buf[0] != '*' || buf[1] != ' ') {
2513 if (strstr(buf, "FETCH") != NULL)
2516 if (ok != IMAP_SUCCESS)
2519 cur_pos = strchr(buf, '{');
2520 if (cur_pos == NULL) {
2524 cur_pos = strchr_cpy(cur_pos + 1, '}', size_str, sizeof(size_str));
2525 if (cur_pos == NULL) {
2529 size_num = atol(size_str);
2530 g_return_val_if_fail(size_num > 0, IMAP_ERROR);
2532 if (*cur_pos != '\0') {
2537 if (recv_bytes_write_to_file(SESSION(session)->sock, size_num, filename) != 0) {
2542 if (imap_gen_recv(session, &buf) != IMAP_SUCCESS) {
2547 if (buf[0] == '\0' || buf[strlen(buf) - 1] != ')') {
2553 ok = imap_cmd_ok(session, NULL);
2558 static gint imap_cmd_append(IMAPSession *session, const gchar *destfolder,
2559 const gchar *file, gint32 *new_uid)
2564 gchar buf[BUFFSIZE], *imapbuf;
2569 g_return_val_if_fail(file != NULL, IMAP_ERROR);
2571 size = get_file_size_as_crlf(file);
2572 if ((fp = fopen(file, "rb")) == NULL) {
2573 FILE_OP_ERROR(file, "fopen");
2576 QUOTE_IF_REQUIRED(destfolder_, destfolder);
2577 imap_gen_send(session, "APPEND %s (\\Seen) {%d}", destfolder_, size);
2579 ok = imap_gen_recv(session, &imapbuf);
2580 if (ok != IMAP_SUCCESS || imapbuf[0] != '+' || imapbuf[1] != ' ') {
2581 log_warning(_("can't append %s to %s\n"), file, destfolder_);
2588 log_print("IMAP4> %s\n", _("(sending file...)"));
2590 while (fgets(buf, sizeof(buf), fp) != NULL) {
2592 if (sock_puts(SESSION(session)->sock, buf) < 0) {
2599 FILE_OP_ERROR(file, "fgets");
2604 sock_puts(SESSION(session)->sock, "");
2608 reply = g_ptr_array_new();
2611 ok = imap_cmd_ok(session, reply);
2612 if (ok != IMAP_SUCCESS)
2613 log_warning(_("can't append message to %s\n"), destfolder_);
2615 (new_uid != NULL) &&
2616 (imap_has_capability(session, "UIDPLUS") && reply->len > 0) &&
2617 ((okmsginfo = g_ptr_array_index(reply, reply->len - 1)) != NULL) &&
2618 (sscanf(okmsginfo, "%*u OK [APPENDUID %*u %u]", &newuid) == 1)) {
2622 ptr_array_free_strings(reply);
2623 g_ptr_array_free(reply, TRUE);
2628 static gint imap_cmd_copy(IMAPSession * session,
2630 const gchar * destfolder, gint32 * new_uid)
2633 gint32 olduid, newuid;
2638 g_return_val_if_fail(destfolder != NULL, IMAP_ERROR);
2639 g_return_val_if_fail(session != NULL, IMAP_ERROR);
2640 g_return_val_if_fail(new_uid != NULL, IMAP_ERROR);
2642 QUOTE_IF_REQUIRED(destfolder_, destfolder);
2643 imap_gen_send(session, "UID COPY %d %s", msgnum, destfolder_);
2645 reply = g_ptr_array_new();
2648 ok = imap_cmd_ok(session, reply);
2649 if (ok != IMAP_SUCCESS)
2650 log_warning(_("can't copy %d to %s\n"), msgnum, destfolder_);
2651 else if (imap_has_capability(session, "UIDPLUS") && reply->len > 0)
2652 if ((okmsginfo = g_ptr_array_index(reply, reply->len - 1)) != NULL &&
2653 sscanf(okmsginfo, "%*u OK [COPYUID %*u %u %u]", &olduid, &newuid) == 2 &&
2657 ptr_array_free_strings(reply);
2658 g_ptr_array_free(reply, TRUE);
2662 gint imap_cmd_envelope(IMAPSession *session, IMAPSet set)
2664 static GString *header_fields = NULL;
2666 if (header_fields == NULL) {
2667 const HeaderEntry *headers, *elem;
2669 headers = procheader_get_headernames(FALSE);
2670 header_fields = g_string_new("");
2672 for (elem = headers; elem->name != NULL; ++elem) {
2673 gint namelen = strlen(elem->name);
2675 /* Header fields ending with space are not rfc822 headers */
2676 if (elem->name[namelen - 1] == ' ')
2679 /* strip : at the of header field */
2680 if(elem->name[namelen - 1] == ':')
2686 g_string_sprintfa(header_fields, "%s%.*s",
2687 header_fields->str[0] != '\0' ? " " : "",
2688 namelen, elem->name);
2693 (session, "UID FETCH %s (UID FLAGS RFC822.SIZE BODY.PEEK[HEADER.FIELDS (%s)])",
2694 set, header_fields->str);
2696 return IMAP_SUCCESS;
2699 static gint imap_cmd_store(IMAPSession *session, IMAPSet set,
2704 imap_gen_send(session, "UID STORE %s %s",
2707 if ((ok = imap_cmd_ok(session, NULL)) != IMAP_SUCCESS) {
2708 log_warning(_("error while imap command: STORE %s %s\n"),
2713 return IMAP_SUCCESS;
2716 static gint imap_cmd_expunge(IMAPSession *session)
2720 imap_gen_send(session, "EXPUNGE");
2721 if ((ok = imap_cmd_ok(session, NULL)) != IMAP_SUCCESS) {
2722 log_warning(_("error while imap command: EXPUNGE\n"));
2726 return IMAP_SUCCESS;
2729 static gint imap_cmd_ok(IMAPSession *session, GPtrArray *argbuf)
2736 while ((ok = imap_gen_recv(session, &buf))
2738 // make sure data is long enough for any substring of buf
2739 data = alloca(strlen(buf) + 1);
2741 if (buf[0] == '*' && buf[1] == ' ') {
2744 g_ptr_array_add(argbuf, g_strdup(buf + 2));
2746 if (sscanf(buf + 2, "%d %s", &num, data) < 2)
2749 if (!strcmp(data, "EXISTS")) {
2750 session->exists = num;
2751 session->folder_content_changed = TRUE;
2754 if(!strcmp(data, "EXPUNGE")) {
2756 session->folder_content_changed = TRUE;
2762 if (sscanf(buf, "%d %s", &cmd_num, data) < 2) {
2765 } else if (cmd_num == session->cmd_count &&
2766 !strcmp(data, "OK")) {
2768 g_ptr_array_add(argbuf, g_strdup(buf));
2770 return IMAP_SUCCESS;
2781 static void imap_gen_send(IMAPSession *session, const gchar *format, ...)
2788 va_start(args, format);
2789 tmp = g_strdup_vprintf(format, args);
2792 session->cmd_count++;
2794 buf = g_strdup_printf("%d %s\r\n", session->cmd_count, tmp);
2795 if (!strncasecmp(tmp, "LOGIN ", 6) && (p = strchr(tmp + 6, ' '))) {
2797 log_print("IMAP4> %d %s ********\n", session->cmd_count, tmp);
2799 log_print("IMAP4> %d %s\n", session->cmd_count, tmp);
2801 sock_write_all(SESSION(session)->sock, buf, strlen(buf));
2806 static gint imap_gen_recv(IMAPSession *session, gchar **buf)
2808 if ((*buf = sock_getline(SESSION(session)->sock)) == NULL)
2813 log_print("IMAP4< %s\n", *buf);
2815 return IMAP_SUCCESS;
2819 /* misc utility functions */
2821 static gchar *strchr_cpy(const gchar *src, gchar ch, gchar *dest, gint len)
2826 tmp = strchr(src, ch);
2830 memcpy(dest, src, MIN(tmp - src, len - 1));
2831 dest[MIN(tmp - src, len - 1)] = '\0';
2836 static gchar *get_quoted(const gchar *src, gchar ch, gchar *dest, gint len)
2838 const gchar *p = src;
2841 g_return_val_if_fail(*p == ch, NULL);
2846 while (*p != '\0' && *p != ch) {
2848 if (*p == '\\' && *(p + 1) != '\0')
2857 return (gchar *)(*p == ch ? p + 1 : p);
2860 static gchar *search_array_contain_str(GPtrArray *array, const gchar *str)
2864 for (i = 0; i < array->len; i++) {
2867 tmp = g_ptr_array_index(array, i);
2868 if (strstr(tmp, str) != NULL)
2875 static gchar *search_array_str(GPtrArray *array, const gchar *str)
2882 for (i = 0; i < array->len; i++) {
2885 tmp = g_ptr_array_index(array, i);
2886 if (!strncmp(tmp, str, len))
2893 static void imap_path_separator_subst(gchar *str, gchar separator)
2896 gboolean in_escape = FALSE;
2898 if (!separator || separator == '/') return;
2900 for (p = str; *p != '\0'; p++) {
2901 if (*p == '/' && !in_escape)
2903 else if (*p == '&' && *(p + 1) != '-' && !in_escape)
2905 else if (*p == '-' && in_escape)
2910 static gchar *imap_modified_utf7_to_locale(const gchar *mutf7_str)
2913 const gchar *from_p;
2916 to = g_malloc(strlen(mutf7_str) + 1);
2919 for (from_p = mutf7_str; *from_p != '\0'; from_p++) {
2920 if (*from_p == '&' && *(from_p + 1) == '-') {
2930 static iconv_t cd = (iconv_t)-1;
2931 static gboolean iconv_ok = TRUE;
2934 size_t norm_utf7_len;
2936 gchar *to_str, *to_p;
2938 gboolean in_escape = FALSE;
2940 if (!iconv_ok) return g_strdup(mutf7_str);
2942 if (cd == (iconv_t)-1) {
2943 cd = iconv_open(conv_get_current_charset_str(), "UTF-7");
2944 if (cd == (iconv_t)-1) {
2945 g_warning("iconv cannot convert UTF-7 to %s\n",
2946 conv_get_current_charset_str());
2948 return g_strdup(mutf7_str);
2952 norm_utf7 = g_string_new(NULL);
2954 for (p = mutf7_str; *p != '\0'; p++) {
2955 /* replace: '&' -> '+',
2957 escaped ',' -> '/' */
2958 if (!in_escape && *p == '&') {
2959 if (*(p + 1) != '-') {
2960 g_string_append_c(norm_utf7, '+');
2963 g_string_append_c(norm_utf7, '&');
2966 } else if (in_escape && *p == ',') {
2967 g_string_append_c(norm_utf7, '/');
2968 } else if (in_escape && *p == '-') {
2969 g_string_append_c(norm_utf7, '-');
2972 g_string_append_c(norm_utf7, *p);
2976 norm_utf7_p = norm_utf7->str;
2977 norm_utf7_len = norm_utf7->len;
2978 to_len = strlen(mutf7_str) * 5;
2979 to_p = to_str = g_malloc(to_len + 1);
2981 if (iconv(cd, (ICONV_CONST gchar **)&norm_utf7_p, &norm_utf7_len,
2982 &to_p, &to_len) == -1) {
2983 g_warning(_("iconv cannot convert UTF-7 to %s\n"),
2984 conv_get_current_charset_str());
2985 g_string_free(norm_utf7, TRUE);
2987 return g_strdup(mutf7_str);
2990 /* second iconv() call for flushing */
2991 iconv(cd, NULL, NULL, &to_p, &to_len);
2992 g_string_free(norm_utf7, TRUE);
2996 #endif /* !HAVE_ICONV */
2999 static gchar *imap_locale_to_modified_utf7(const gchar *from)
3002 const gchar *from_p;
3005 to = g_malloc(strlen(from) * 2 + 1);
3008 for (from_p = from; *from_p != '\0'; from_p++) {
3009 if (*from_p == '&') {
3019 static iconv_t cd = (iconv_t)-1;
3020 static gboolean iconv_ok = TRUE;
3021 gchar *norm_utf7, *norm_utf7_p;
3022 size_t from_len, norm_utf7_len;
3024 gchar *from_tmp, *to, *p;
3025 gboolean in_escape = FALSE;
3027 if (!iconv_ok) return g_strdup(from);
3029 if (cd == (iconv_t)-1) {
3030 cd = iconv_open("UTF-7", conv_get_current_charset_str());
3031 if (cd == (iconv_t)-1) {
3032 g_warning("iconv cannot convert %s to UTF-7\n",
3033 conv_get_current_charset_str());
3035 return g_strdup(from);
3039 Xstrdup_a(from_tmp, from, return g_strdup(from));
3040 from_len = strlen(from);
3041 norm_utf7_len = from_len * 5;
3042 Xalloca(norm_utf7, norm_utf7_len + 1, return g_strdup(from));
3043 norm_utf7_p = norm_utf7;
3045 #define IS_PRINT(ch) (isprint(ch) && isascii(ch))
3047 while (from_len > 0) {
3048 if (*from_tmp == '+') {
3049 *norm_utf7_p++ = '+';
3050 *norm_utf7_p++ = '-';
3054 } else if (IS_PRINT(*from_tmp)) {
3055 /* printable ascii char */
3056 *norm_utf7_p = *from_tmp;
3062 size_t mb_len = 0, conv_len = 0;
3064 /* unprintable char: convert to UTF-7 */
3066 while (!IS_PRINT(*p) && conv_len < from_len) {
3067 mb_len = mblen(p, MB_LEN_MAX);
3069 g_warning("wrong multibyte sequence\n");
3070 return g_strdup(from);
3076 from_len -= conv_len;
3077 if (iconv(cd, (ICONV_CONST gchar **)&from_tmp,
3079 &norm_utf7_p, &norm_utf7_len) == -1) {
3080 g_warning("iconv cannot convert %s to UTF-7\n",
3081 conv_get_current_charset_str());
3082 return g_strdup(from);
3085 /* second iconv() call for flushing */
3086 iconv(cd, NULL, NULL, &norm_utf7_p, &norm_utf7_len);
3092 *norm_utf7_p = '\0';
3093 to_str = g_string_new(NULL);
3094 for (p = norm_utf7; p < norm_utf7_p; p++) {
3095 /* replace: '&' -> "&-",
3098 BASE64 '/' -> ',' */
3099 if (!in_escape && *p == '&') {
3100 g_string_append(to_str, "&-");
3101 } else if (!in_escape && *p == '+') {
3102 if (*(p + 1) == '-') {
3103 g_string_append_c(to_str, '+');
3106 g_string_append_c(to_str, '&');
3109 } else if (in_escape && *p == '/') {
3110 g_string_append_c(to_str, ',');
3111 } else if (in_escape && *p == '-') {
3112 g_string_append_c(to_str, '-');
3115 g_string_append_c(to_str, *p);
3121 g_string_append_c(to_str, '-');
3125 g_string_free(to_str, FALSE);
3128 #endif /* !HAVE_ICONV */
3131 static gboolean imap_rename_folder_func(GNode *node, gpointer data)
3133 FolderItem *item = node->data;
3134 gchar **paths = data;
3135 const gchar *oldpath = paths[0];
3136 const gchar *newpath = paths[1];
3138 gchar *new_itempath;
3141 oldpathlen = strlen(oldpath);
3142 if (strncmp(oldpath, item->path, oldpathlen) != 0) {
3143 g_warning("path doesn't match: %s, %s\n", oldpath, item->path);
3147 base = item->path + oldpathlen;
3148 while (*base == G_DIR_SEPARATOR) base++;
3150 new_itempath = g_strdup(newpath);
3152 new_itempath = g_strconcat(newpath, G_DIR_SEPARATOR_S, base,
3155 item->path = new_itempath;
3160 static gint get_list_of_uids(Folder *folder, IMAPFolderItem *item, GSList **msgnum_list)
3162 gint ok, nummsgs = 0, lastuid_old;
3163 IMAPSession *session;
3164 GSList *uidlist, *elem;
3167 session = imap_session_get(folder);
3168 g_return_val_if_fail(session != NULL, -1);
3170 ok = imap_select(session, IMAP_FOLDER(folder), item->item.path,
3171 NULL, NULL, NULL, NULL);
3172 if (ok != IMAP_SUCCESS)
3175 cmd_buf = g_strdup_printf("UID %d:*", item->lastuid + 1);
3176 ok = imap_cmd_search(session, cmd_buf, &uidlist);
3179 if (ok == IMAP_SOCKET) {
3180 session_destroy((Session *)session);
3181 ((RemoteFolder *)folder)->session = NULL;
3185 if (ok != IMAP_SUCCESS) {
3189 argbuf = g_ptr_array_new();
3191 cmd_buf = g_strdup_printf("UID FETCH %d:* (UID)", item->lastuid + 1);
3192 imap_gen_send(session, cmd_buf);
3194 ok = imap_cmd_ok(session, argbuf);
3195 if (ok != IMAP_SUCCESS) {
3196 ptr_array_free_strings(argbuf);
3197 g_ptr_array_free(argbuf, TRUE);
3201 for(i = 0; i < argbuf->len; i++) {
3204 if((ret = sscanf(g_ptr_array_index(argbuf, i),
3205 "%*d FETCH (UID %d)", &msgnum)) == 1)
3206 uidlist = g_slist_prepend(uidlist, GINT_TO_POINTER(msgnum));
3208 ptr_array_free_strings(argbuf);
3209 g_ptr_array_free(argbuf, TRUE);
3212 lastuid_old = item->lastuid;
3213 *msgnum_list = g_slist_copy(item->uid_list);
3214 nummsgs = g_slist_length(*msgnum_list);
3215 debug_print("Got %d uids from cache\n", g_slist_length(item->uid_list));
3217 for (elem = uidlist; elem != NULL; elem = g_slist_next(elem)) {
3220 msgnum = GPOINTER_TO_INT(elem->data);
3221 if (msgnum > lastuid_old) {
3222 *msgnum_list = g_slist_prepend(*msgnum_list, GINT_TO_POINTER(msgnum));
3223 item->uid_list = g_slist_prepend(item->uid_list, GINT_TO_POINTER(msgnum));
3226 if(msgnum > item->lastuid)
3227 item->lastuid = msgnum;
3230 g_slist_free(uidlist);
3235 gint imap_get_num_list(Folder *folder, FolderItem *_item, GSList **msgnum_list)
3237 IMAPFolderItem *item = (IMAPFolderItem *)_item;
3238 IMAPSession *session;
3239 gint ok, nummsgs = 0, exists, recent, unseen, uid_val, uid_next;
3242 gboolean selected_folder;
3244 g_return_val_if_fail(folder != NULL, -1);
3245 g_return_val_if_fail(item != NULL, -1);
3246 g_return_val_if_fail(item->item.path != NULL, -1);
3247 g_return_val_if_fail(FOLDER_CLASS(folder) == &imap_class, -1);
3248 g_return_val_if_fail(folder->account != NULL, -1);
3250 session = imap_session_get(folder);
3251 g_return_val_if_fail(session != NULL, -1);
3253 selected_folder = (session->mbox != NULL) &&
3254 (!strcmp(session->mbox, item->item.path));
3255 if (selected_folder) {
3256 ok = imap_cmd_noop(session);
3257 if (ok != IMAP_SUCCESS)
3259 exists = session->exists;
3261 ok = imap_status(session, IMAP_FOLDER(folder), item->item.path,
3262 &exists, &recent, &uid_next, &uid_val, &unseen);
3263 if (ok != IMAP_SUCCESS)
3267 /* If old uid_next matches new uid_next we can be sure no message
3268 was added to the folder */
3269 if (( selected_folder && !session->folder_content_changed) ||
3270 (!selected_folder && uid_next == item->uid_next)) {
3271 nummsgs = g_slist_length(item->uid_list);
3273 /* If number of messages is still the same we
3274 know our caches message numbers are still valid,
3275 otherwise if the number of messages has decrease
3276 we discard our cache to start a new scan to find
3277 out which numbers have been removed */
3278 if (exists == nummsgs) {
3279 *msgnum_list = g_slist_copy(item->uid_list);
3281 } else if (exists < nummsgs) {
3282 debug_print("Freeing imap uid cache");
3284 g_slist_free(item->uid_list);
3285 item->uid_list = NULL;
3288 if (!selected_folder)
3289 item->uid_next = uid_next;
3292 *msgnum_list = NULL;
3296 nummsgs = get_list_of_uids(folder, item, &uidlist);
3298 if (nummsgs != exists) {
3299 /* Cache contains more messages then folder, we have cached
3300 an old UID of a message that was removed and new messages
3301 have been added too, otherwise the uid_next check would
3303 debug_print("Freeing imap uid cache");
3305 g_slist_free(item->uid_list);
3306 item->uid_list = NULL;
3308 g_slist_free(*msgnum_list);
3310 nummsgs = get_list_of_uids(folder, item, &uidlist);
3313 *msgnum_list = uidlist;
3315 dir = folder_item_get_path((FolderItem *)item);
3316 debug_print("removing old messages from %s\n", dir);
3317 remove_numbered_files_not_in_list(dir, *msgnum_list);
3323 static MsgInfo *imap_parse_msg(const gchar *file, FolderItem *item)
3328 flags.perm_flags = MSG_NEW|MSG_UNREAD;
3329 flags.tmp_flags = 0;
3331 g_return_val_if_fail(item != NULL, NULL);
3332 g_return_val_if_fail(file != NULL, NULL);
3334 if (item->stype == F_QUEUE) {
3335 MSG_SET_TMP_FLAGS(flags, MSG_QUEUED);
3336 } else if (item->stype == F_DRAFT) {
3337 MSG_SET_TMP_FLAGS(flags, MSG_DRAFT);
3340 msginfo = procheader_parse_file(file, flags, FALSE, FALSE);
3341 if (!msginfo) return NULL;
3343 msginfo->folder = item;
3348 GSList *imap_get_msginfos(Folder *folder, FolderItem *item, GSList *msgnum_list)
3350 IMAPSession *session;
3351 MsgInfoList *ret = NULL;
3354 g_return_val_if_fail(folder != NULL, NULL);
3355 g_return_val_if_fail(item != NULL, NULL);
3356 g_return_val_if_fail(msgnum_list != NULL, NULL);
3358 session = imap_session_get(folder);
3359 g_return_val_if_fail(session != NULL, NULL);
3361 ok = imap_select(session, IMAP_FOLDER(folder), item->path,
3362 NULL, NULL, NULL, NULL);
3363 if (ok != IMAP_SUCCESS)
3366 if (!(item->stype == F_QUEUE || item->stype == F_DRAFT)) {
3367 ret = g_slist_concat(ret,
3368 imap_get_uncached_messages(
3369 session, item, msgnum_list));
3371 MsgNumberList *sorted_list, *elem;
3372 gint startnum, lastnum;
3374 sorted_list = g_slist_sort(g_slist_copy(msgnum_list), g_int_compare);
3376 startnum = lastnum = GPOINTER_TO_INT(sorted_list->data);
3378 for (elem = sorted_list;; elem = g_slist_next(elem)) {
3382 num = GPOINTER_TO_INT(elem->data);
3384 if (num > lastnum + 1 || elem == NULL) {
3386 for (i = startnum; i <= lastnum; ++i) {
3389 file = imap_fetch_msg(folder, item, i);
3391 MsgInfo *msginfo = imap_parse_msg(file, item);
3392 if (msginfo != NULL) {
3393 msginfo->msgnum = i;
3394 ret = g_slist_append(ret, msginfo);
3408 g_slist_free(sorted_list);
3414 MsgInfo *imap_get_msginfo(Folder *folder, FolderItem *item, gint uid)
3416 MsgInfo *msginfo = NULL;
3417 MsgInfoList *msginfolist;
3418 MsgNumberList numlist;
3420 numlist.next = NULL;
3421 numlist.data = GINT_TO_POINTER(uid);
3423 msginfolist = imap_get_msginfos(folder, item, &numlist);
3424 if (msginfolist != NULL) {
3425 msginfo = msginfolist->data;
3426 g_slist_free(msginfolist);
3432 gboolean imap_check_msgnum_validity(Folder *folder, FolderItem *_item)
3434 IMAPSession *session;
3435 IMAPFolderItem *item = (IMAPFolderItem *)_item;
3436 gint ok, exists = 0, recent = 0, unseen = 0;
3437 guint32 uid_next, uid_validity = 0;
3439 g_return_val_if_fail(folder != NULL, FALSE);
3440 g_return_val_if_fail(item != NULL, FALSE);
3441 g_return_val_if_fail(item->item.folder != NULL, FALSE);
3442 g_return_val_if_fail(FOLDER_CLASS(item->item.folder) == &imap_class, FALSE);
3444 session = imap_session_get(folder);
3445 g_return_val_if_fail(session != NULL, FALSE);
3447 ok = imap_status(session, IMAP_FOLDER(folder), item->item.path,
3448 &exists, &recent, &uid_next, &uid_validity, &unseen);
3449 if (ok != IMAP_SUCCESS)
3452 if(item->item.mtime == uid_validity)
3455 debug_print("Freeing imap uid cache\n");
3457 g_slist_free(item->uid_list);
3458 item->uid_list = NULL;
3460 item->item.mtime = uid_validity;
3462 imap_delete_all_cached_messages((FolderItem *)item);
3467 void imap_change_flags(Folder *folder, FolderItem *item, MsgInfo *msginfo, MsgPermFlags newflags)
3469 IMAPSession *session;
3470 IMAPFlags flags_set = 0, flags_unset = 0;
3471 gint ok = IMAP_SUCCESS;
3472 MsgNumberList numlist;
3474 g_return_if_fail(folder != NULL);
3475 g_return_if_fail(folder->klass == &imap_class);
3476 g_return_if_fail(item != NULL);
3477 g_return_if_fail(item->folder == folder);
3478 g_return_if_fail(msginfo != NULL);
3479 g_return_if_fail(msginfo->folder == item);
3481 session = imap_session_get(folder);
3482 if (!session) return;
3484 if ((ok = imap_select(session, IMAP_FOLDER(folder), msginfo->folder->path,
3485 NULL, NULL, NULL, NULL)) != IMAP_SUCCESS)
3488 if (!MSG_IS_MARKED(msginfo->flags) && (newflags & MSG_MARKED))
3489 flags_set |= IMAP_FLAG_FLAGGED;
3490 if ( MSG_IS_MARKED(msginfo->flags) && !(newflags & MSG_MARKED))
3491 flags_unset |= IMAP_FLAG_FLAGGED;
3493 if (!MSG_IS_UNREAD(msginfo->flags) && (newflags & MSG_UNREAD))
3494 flags_unset |= IMAP_FLAG_SEEN;
3495 if ( MSG_IS_UNREAD(msginfo->flags) && !(newflags & MSG_UNREAD))
3496 flags_set |= IMAP_FLAG_SEEN;
3498 if (!MSG_IS_REPLIED(msginfo->flags) && (newflags & MSG_REPLIED))
3499 flags_set |= IMAP_FLAG_ANSWERED;
3500 if ( MSG_IS_REPLIED(msginfo->flags) && !(newflags & MSG_REPLIED))
3501 flags_set |= IMAP_FLAG_ANSWERED;
3503 numlist.next = NULL;
3504 numlist.data = GINT_TO_POINTER(msginfo->msgnum);
3507 ok = imap_set_message_flags(session, &numlist, flags_set, TRUE);
3508 if (ok != IMAP_SUCCESS) return;
3512 ok = imap_set_message_flags(session, &numlist, flags_unset, FALSE);
3513 if (ok != IMAP_SUCCESS) return;
3516 msginfo->flags.perm_flags = newflags;