2 * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2003 Hiroyuki Yamamoto
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
49 #include "procheader.h"
50 #include "prefs_account.h"
53 #include "inputdialog.h"
56 typedef struct _IMAPFolder IMAPFolder;
57 typedef struct _IMAPSession IMAPSession;
58 typedef struct _IMAPNameSpace IMAPNameSpace;
59 typedef struct _IMAPFolderItem IMAPFolderItem;
61 #include "prefs_account.h"
63 #define IMAP_FOLDER(obj) ((IMAPFolder *)obj)
64 #define IMAP_SESSION(obj) ((IMAPSession *)obj)
70 /* list of IMAPNameSpace */
82 time_t last_access_time;
83 gboolean authenticated;
85 gboolean folder_content_changed;
95 #define IMAP_SUCCESS 0
97 #define IMAP_AUTHFAIL 3
98 #define IMAP_PROTOCOL 4
103 #define IMAPBUFSIZE 8192
104 #define IMAPCMDLIMIT 1000
108 IMAP_FLAG_SEEN = 1 << 0,
109 IMAP_FLAG_ANSWERED = 1 << 1,
110 IMAP_FLAG_FLAGGED = 1 << 2,
111 IMAP_FLAG_DELETED = 1 << 3,
112 IMAP_FLAG_DRAFT = 1 << 4
115 #define IMAP_IS_SEEN(flags) ((flags & IMAP_FLAG_SEEN) != 0)
116 #define IMAP_IS_ANSWERED(flags) ((flags & IMAP_FLAG_ANSWERED) != 0)
117 #define IMAP_IS_FLAGGED(flags) ((flags & IMAP_FLAG_FLAGGED) != 0)
118 #define IMAP_IS_DELETED(flags) ((flags & IMAP_FLAG_DELETED) != 0)
119 #define IMAP_IS_DRAFT(flags) ((flags & IMAP_FLAG_DRAFT) != 0)
122 #define IMAP4_PORT 143
124 #define IMAPS_PORT 993
127 #define QUOTE_IF_REQUIRED(out, str) \
129 if (*str != '"' && strpbrk(str, " \t(){}%*") != NULL) { \
133 len = strlen(str) + 3; \
134 Xalloca(__tmp, len, return IMAP_ERROR); \
135 g_snprintf(__tmp, len, "\"%s\"", str); \
138 Xstrdup_a(out, str, return IMAP_ERROR); \
142 typedef gchar * IMAPSet;
144 struct _IMAPFolderItem
153 static Folder *imap_folder_new(const gchar * name, const gchar * path);
154 static void imap_folder_destroy(Folder * folder);
156 static IMAPSession *imap_session_new(const PrefsAccount * account);
157 static void imap_session_authenticate(IMAPSession * session,
158 const PrefsAccount * account);
159 static void imap_session_destroy(Session * session);
161 static gchar *imap_fetch_msg(Folder * folder, FolderItem * item, gint uid);
162 static gint imap_add_msg(Folder * folder,
164 const gchar * file, gboolean remove_source);
166 static gint imap_copy_msg(Folder * folder,
167 FolderItem * dest, MsgInfo * msginfo);
169 static gint imap_remove_msg(Folder * folder, FolderItem * item, gint uid);
170 static gint imap_remove_all_msg(Folder * folder, FolderItem * item);
172 static gboolean imap_is_msg_changed(Folder * folder,
173 FolderItem * item, MsgInfo * msginfo);
175 static void imap_scan_tree(Folder * folder);
177 static gint imap_create_tree(Folder * folder);
179 static FolderItem *imap_create_folder(Folder * folder,
182 static gint imap_rename_folder(Folder * folder,
183 FolderItem * item, const gchar * name);
184 static gint imap_remove_folder(Folder * folder, FolderItem * item);
187 static void imap_folder_init (Folder *folder,
191 static FolderItem *imap_folder_item_new (Folder *folder);
192 static void imap_folder_item_destroy (Folder *folder,
195 static IMAPSession *imap_session_get (Folder *folder);
197 static gint imap_scan_tree_recursive (IMAPSession *session,
199 static GSList *imap_parse_list (IMAPFolder *folder,
200 IMAPSession *session,
201 const gchar *real_path,
204 static void imap_create_missing_folders (Folder *folder);
205 static FolderItem *imap_create_special_folder
207 SpecialFolderItemType stype,
210 static gint imap_do_copy (Folder *folder,
213 gboolean remove_source);
215 static void imap_delete_all_cached_messages (FolderItem *item);
218 static SockInfo *imap_open (const gchar *server,
222 static SockInfo *imap_open (const gchar *server,
227 static SockInfo *imap_open_tunnel(const gchar *server,
228 const gchar *tunnelcmd,
231 static SockInfo *imap_open_tunnel(const gchar *server,
232 const gchar *tunnelcmd);
236 static SockInfo *imap_init_sock(SockInfo *sock, SSLType ssl_type);
238 static SockInfo *imap_init_sock(SockInfo *sock);
241 static gint imap_set_message_flags (IMAPSession *session,
242 MsgNumberList *numlist,
245 static gint imap_select (IMAPSession *session,
251 guint32 *uid_validity);
252 static gint imap_status (IMAPSession *session,
258 guint32 *uid_validity,
261 static void imap_parse_namespace (IMAPSession *session,
263 static void imap_get_namespace_by_list (IMAPSession *session,
265 static IMAPNameSpace *imap_find_namespace (IMAPFolder *folder,
267 static gchar imap_get_path_separator (IMAPFolder *folder,
269 static gchar *imap_get_real_path (IMAPFolder *folder,
272 static gchar *imap_parse_atom (SockInfo *sock,
277 static MsgFlags imap_parse_flags (const gchar *flag_str);
278 static MsgInfo *imap_parse_envelope (SockInfo *sock,
281 static gint imap_greeting (IMAPSession *session);
282 static gboolean imap_has_capability (IMAPSession *session,
284 void imap_free_capabilities (IMAPSession *session);
285 static const IMAPSet numberlist_to_imapset
286 (MsgNumberList *list);
288 /* low-level IMAP4rev1 commands */
289 static gint imap_cmd_login (IMAPSession *sock,
292 static gint imap_cmd_logout (IMAPSession *sock);
293 static gint imap_cmd_noop (IMAPSession *sock);
294 static gint imap_cmd_starttls (IMAPSession *sock);
295 static gint imap_cmd_namespace (IMAPSession *sock,
297 static gint imap_cmd_list (IMAPSession *session,
299 const gchar *mailbox,
301 static gint imap_cmd_do_select (IMAPSession *sock,
307 guint32 *uid_validity);
308 static gint imap_cmd_select (IMAPSession *sock,
313 guint32 *uid_validity);
314 static gint imap_cmd_examine (IMAPSession *sock,
319 guint32 *uid_validity);
320 static gint imap_cmd_create (IMAPSession *sock,
321 const gchar *folder);
322 static gint imap_cmd_rename (IMAPSession *sock,
323 const gchar *oldfolder,
324 const gchar *newfolder);
325 static gint imap_cmd_delete (IMAPSession *sock,
326 const gchar *folder);
327 static gint imap_cmd_envelope (IMAPSession *sock,
329 static gint imap_cmd_fetch (IMAPSession *sock,
331 const gchar *filename);
332 static gint imap_cmd_append (IMAPSession *session,
333 const gchar *destfolder,
336 static gint imap_cmd_copy (IMAPSession *session,
338 const gchar *destfolder,
340 static gint imap_cmd_store (IMAPSession *sock,
343 static gint imap_cmd_expunge (IMAPSession *sock);
345 static gint imap_cmd_ok (IMAPSession *session,
347 static void imap_gen_send (IMAPSession *sock,
348 const gchar *format, ...);
349 static gint imap_gen_recv (IMAPSession *sock,
352 /* misc utility functions */
353 static gchar *strchr_cpy (const gchar *src,
357 static gchar *get_quoted (const gchar *src,
361 static gchar *search_array_contain_str (GPtrArray *array,
363 static gchar *search_array_str (GPtrArray *array,
365 static void imap_path_separator_subst (gchar *str,
368 static gchar *imap_modified_utf7_to_locale (const gchar *mutf7_str);
369 static gchar *imap_locale_to_modified_utf7 (const gchar *from);
371 static gboolean imap_rename_folder_func (GNode *node,
373 static gint imap_get_num_list (Folder *folder,
376 static GSList *imap_get_msginfos (Folder *folder,
378 GSList *msgnum_list);
379 static MsgInfo *imap_get_msginfo (Folder *folder,
382 static gboolean imap_check_msgnum_validity (Folder *folder,
384 static void imap_change_flags (Folder *folder,
387 MsgPermFlags newflags);
389 FolderClass imap_class =
395 /* Folder functions */
401 /* FolderItem functions */
402 imap_folder_item_new,
403 imap_folder_item_destroy,
411 imap_check_msgnum_validity,
413 /* Message functions */
425 FolderClass *imap_get_class(void)
430 Folder *imap_folder_new(const gchar *name, const gchar *path)
434 folder = (Folder *)g_new0(IMAPFolder, 1);
435 folder->klass = &imap_class;
436 imap_folder_init(folder, name, path);
441 void imap_folder_destroy(Folder *folder)
445 dir = folder_get_path(folder);
446 if (is_dir_exist(dir))
447 remove_dir_recursive(dir);
450 folder_remote_folder_destroy(REMOTE_FOLDER(folder));
453 static void imap_folder_init(Folder *folder, const gchar *name,
456 folder_remote_folder_init((Folder *)folder, name, path);
459 static FolderItem *imap_folder_item_new(Folder *folder)
461 IMAPFolderItem *item;
463 item = g_new0(IMAPFolderItem, 1);
466 item->uid_list = NULL;
468 return (FolderItem *)item;
471 static void imap_folder_item_destroy(Folder *folder, FolderItem *_item)
473 IMAPFolderItem *item = (IMAPFolderItem *)_item;
475 g_return_if_fail(item != NULL);
476 g_slist_free(item->uid_list);
481 static gboolean imap_reset_uid_lists_func(GNode *node, gpointer data)
483 IMAPFolderItem *item = (IMAPFolderItem *)node->data;
487 g_slist_free(item->uid_list);
488 item->uid_list = NULL;
493 static void imap_reset_uid_lists(Folder *folder)
495 if(folder->node == NULL)
498 /* Destroy all uid lists and rest last uid */
499 g_node_traverse(folder->node, G_IN_ORDER, G_TRAVERSE_ALL, -1, imap_reset_uid_lists_func, NULL);
502 static IMAPSession *imap_session_get(Folder *folder)
504 RemoteFolder *rfolder = REMOTE_FOLDER(folder);
505 IMAPSession *session = NULL;
508 g_return_val_if_fail(folder != NULL, NULL);
509 g_return_val_if_fail(FOLDER_CLASS(folder) == &imap_class, NULL);
510 g_return_val_if_fail(folder->account != NULL, NULL);
513 port = folder->account->set_imapport ? folder->account->imapport
514 : folder->account->ssl_imap == SSL_TUNNEL
515 ? IMAPS_PORT : IMAP4_PORT;
517 port = folder->account->set_imapport ? folder->account->imapport
521 /* Make sure we have a session */
522 if (rfolder->session != NULL) {
523 session = IMAP_SESSION(rfolder->session);
525 imap_reset_uid_lists(folder);
526 session = imap_session_new(folder->account);
531 if (SESSION(session)->sock->state == CONN_DISCONNECTED) {
532 debug_print("IMAP server disconnected\n");
533 session_destroy(SESSION(session));
534 imap_reset_uid_lists(folder);
535 session = imap_session_new(folder->account);
538 /* Make sure session is authenticated */
539 if (!IMAP_SESSION(session)->authenticated)
540 imap_session_authenticate(IMAP_SESSION(session), folder->account);
541 if (!IMAP_SESSION(session)->authenticated) {
542 session_destroy(SESSION(session));
543 rfolder->session = NULL;
547 /* Make sure we have parsed the IMAP namespace */
548 imap_parse_namespace(IMAP_SESSION(session),
549 IMAP_FOLDER(folder));
551 /* I think the point of this code is to avoid sending a
552 * keepalive if we've used the session recently and therefore
553 * think it's still alive. Unfortunately, most of the code
554 * does not yet check for errors on the socket, and so if the
555 * connection drops we don't notice until the timeout expires.
556 * A better solution than sending a NOOP every time would be
557 * for every command to be prepared to retry until it is
558 * successfully sent. -- mbp */
559 if (time(NULL) - session->last_access_time > SESSION_TIMEOUT) {
560 /* verify that the session is still alive */
561 if (imap_cmd_noop(session) != IMAP_SUCCESS) {
562 /* Check if this is the first try to establish a
563 connection, if yes we don't try to reconnect */
564 if (rfolder->session == NULL) {
565 log_warning(_("Connecting %s:%d failed"),
566 folder->account->recv_server, port);
567 session_destroy(SESSION(session));
570 log_warning(_("IMAP4 connection to %s:%d has been"
571 " disconnected. Reconnecting...\n"),
572 folder->account->recv_server, port);
573 session_destroy(SESSION(session));
574 /* Clear folders session to make imap_session_get create
575 a new session, because of rfolder->session == NULL
576 it will not try to reconnect again and so avoid an
578 rfolder->session = NULL;
579 session = imap_session_get(folder);
584 rfolder->session = SESSION(session);
586 session->last_access_time = time(NULL);
588 return IMAP_SESSION(session);
591 IMAPSession *imap_session_new(const PrefsAccount *account)
593 IMAPSession *session;
599 /* FIXME: IMAP over SSL only... */
602 port = account->set_imapport ? account->imapport
603 : account->ssl_imap == SSL_TUNNEL ? IMAPS_PORT : IMAP4_PORT;
604 ssl_type = account->ssl_imap;
606 port = account->set_imapport ? account->imapport
610 if (account->set_tunnelcmd) {
611 log_message(_("creating tunneled IMAP4 connection\n"));
613 if ((imap_sock = imap_open_tunnel(account->recv_server,
617 if ((imap_sock = imap_open_tunnel(account->recv_server,
618 account->tunnelcmd)) == NULL)
622 g_return_val_if_fail(account->recv_server != NULL, NULL);
624 log_message(_("creating IMAP4 connection to %s:%d ...\n"),
625 account->recv_server, port);
628 if ((imap_sock = imap_open(account->recv_server, port,
631 if ((imap_sock = imap_open(account->recv_server, port)) == NULL)
636 session = g_new0(IMAPSession, 1);
637 session_init(SESSION(session));
638 SESSION(session)->type = SESSION_IMAP;
639 SESSION(session)->server = g_strdup(account->recv_server);
640 SESSION(session)->sock = imap_sock;
642 SESSION(session)->destroy = imap_session_destroy;
644 session->capability = NULL;
646 session->mbox = NULL;
647 session->authenticated = is_preauth;
648 session->cmd_count = 0;
650 /* Only need to log in if the connection was not PREAUTH */
651 if (imap_greeting(session) != IMAP_SUCCESS) {
652 session_destroy(SESSION(session));
657 if (account->ssl_imap == SSL_STARTTLS && imap_has_capability(session, "STARTTLS")) {
660 ok = imap_cmd_starttls(session);
661 if (ok != IMAP_SUCCESS) {
662 log_warning(_("Can't start TLS session.\n"));
663 session_destroy(SESSION(session));
666 if (!ssl_init_socket_with_method(SESSION(session)->sock, SSL_METHOD_TLSv1)) {
667 session_destroy(SESSION(session));
671 imap_free_capabilities(session);
672 session->authenticated = is_preauth;
673 session->cmd_count = 1;
675 if (imap_greeting(session) != IMAP_SUCCESS) {
676 session_destroy(SESSION(session));
681 log_message("IMAP connection is %s-authenticated\n",
682 (is_preauth) ? "pre" : "un");
687 void imap_session_authenticate(IMAPSession *session, const PrefsAccount *account)
691 g_return_if_fail(account->userid != NULL);
693 pass = account->passwd;
696 tmp_pass = input_dialog_query_password(account->recv_server, account->userid);
699 Xstrdup_a(pass, tmp_pass, {g_free(tmp_pass); return;});
703 if (imap_cmd_login(session, account->userid, pass) != IMAP_SUCCESS) {
704 imap_cmd_logout(session);
708 session->authenticated = TRUE;
711 void imap_session_destroy(Session *session)
713 sock_close(session->sock);
714 session->sock = NULL;
716 g_free(IMAP_SESSION(session)->mbox);
717 imap_free_capabilities(IMAP_SESSION(session));
720 gchar *imap_fetch_msg(Folder *folder, FolderItem *item, gint uid)
722 gchar *path, *filename;
723 IMAPSession *session;
726 g_return_val_if_fail(folder != NULL, NULL);
727 g_return_val_if_fail(item != NULL, NULL);
729 path = folder_item_get_path(item);
730 if (!is_dir_exist(path))
732 filename = g_strconcat(path, G_DIR_SEPARATOR_S, itos(uid), NULL);
735 if (is_file_exist(filename)) {
736 debug_print("message %d has been already cached.\n", uid);
740 session = imap_session_get(folder);
746 ok = imap_select(session, IMAP_FOLDER(folder), item->path,
747 NULL, NULL, NULL, NULL);
748 if (ok != IMAP_SUCCESS) {
749 g_warning("can't select mailbox %s\n", item->path);
754 debug_print("getting message %d...\n", uid);
755 ok = imap_cmd_fetch(session, (guint32)uid, filename);
757 if (ok != IMAP_SUCCESS) {
758 g_warning("can't fetch message %d\n", uid);
766 gint imap_add_msg(Folder *folder, FolderItem *dest, const gchar *file,
767 gboolean remove_source)
770 IMAPSession *session;
773 g_return_val_if_fail(folder != NULL, -1);
774 g_return_val_if_fail(dest != NULL, -1);
775 g_return_val_if_fail(file != NULL, -1);
777 session = imap_session_get(folder);
778 if (!session) return -1;
780 destdir = imap_get_real_path(IMAP_FOLDER(folder), dest->path);
781 ok = imap_cmd_append(session, destdir, file, &newuid);
784 if (ok != IMAP_SUCCESS) {
785 g_warning("can't append message %s\n", file);
790 if (unlink(file) < 0)
791 FILE_OP_ERROR(file, "unlink");
797 static gint imap_do_copy(Folder *folder, FolderItem *dest, MsgInfo *msginfo,
798 gboolean remove_source)
801 IMAPSession *session;
805 g_return_val_if_fail(folder != NULL, -1);
806 g_return_val_if_fail(FOLDER_CLASS(folder) == &imap_class, -1);
807 g_return_val_if_fail(dest != NULL, -1);
808 g_return_val_if_fail(msginfo != NULL, -1);
810 session = imap_session_get(folder);
811 if (!session) return -1;
813 if (msginfo->folder == dest) {
814 g_warning("the src folder is identical to the dest.\n");
818 destdir = imap_get_real_path(IMAP_FOLDER(folder), dest->path);
820 /* ensure source folder selected */
821 ok = imap_select(session, IMAP_FOLDER(folder), msginfo->folder->path,
822 NULL, NULL, NULL, NULL);
823 if (ok != IMAP_SUCCESS)
827 debug_print("Moving message %s%c%d to %s ...\n",
828 msginfo->folder->path, G_DIR_SEPARATOR,
829 msginfo->msgnum, destdir);
831 debug_print("Copying message %s%c%d to %s ...\n",
832 msginfo->folder->path, G_DIR_SEPARATOR,
833 msginfo->msgnum, destdir);
835 ok = imap_cmd_copy(session, msginfo->msgnum, destdir, &newuid);
837 if (ok == IMAP_SUCCESS && remove_source) {
838 MsgNumberList numlist;
841 numlist.data = GINT_TO_POINTER(msginfo->msgnum);
843 imap_set_message_flags(session, &numlist,
844 IMAP_FLAG_DELETED, TRUE);
845 ok = imap_cmd_expunge(session);
850 if (ok == IMAP_SUCCESS)
856 gint imap_copy_msg(Folder *folder, FolderItem *dest, MsgInfo *msginfo)
861 g_return_val_if_fail(folder != NULL, -1);
862 g_return_val_if_fail(dest != NULL, -1);
863 g_return_val_if_fail(msginfo != NULL, -1);
864 g_return_val_if_fail(msginfo->folder != NULL, -1);
866 if (folder == msginfo->folder->folder)
867 return imap_do_copy(folder, dest, msginfo, FALSE);
869 srcfile = procmsg_get_message_file(msginfo);
870 if (!srcfile) return -1;
872 ret = imap_add_msg(folder, dest, srcfile, FALSE);
879 gint imap_remove_msg(Folder *folder, FolderItem *item, gint uid)
882 IMAPSession *session;
884 MsgNumberList numlist;
886 g_return_val_if_fail(folder != NULL, -1);
887 g_return_val_if_fail(FOLDER_CLASS(folder) == &imap_class, -1);
888 g_return_val_if_fail(item != NULL, -1);
890 session = imap_session_get(folder);
891 if (!session) return -1;
893 ok = imap_select(session, IMAP_FOLDER(folder), item->path,
894 NULL, NULL, NULL, NULL);
895 if (ok != IMAP_SUCCESS)
899 numlist.data = GINT_TO_POINTER(uid);
901 ok = imap_set_message_flags
902 (IMAP_SESSION(REMOTE_FOLDER(folder)->session),
903 &numlist, IMAP_FLAG_DELETED, TRUE);
904 if (ok != IMAP_SUCCESS) {
905 log_warning(_("can't set deleted flags: %d\n"), uid);
909 ok = imap_cmd_expunge(session);
910 if (ok != IMAP_SUCCESS) {
911 log_warning(_("can't expunge\n"));
915 dir = folder_item_get_path(item);
916 if (is_dir_exist(dir))
917 remove_numbered_files(dir, uid, uid);
923 gint imap_remove_all_msg(Folder *folder, FolderItem *item)
925 gint exists, recent, unseen;
926 guint32 uid_validity;
928 IMAPSession *session;
931 g_return_val_if_fail(folder != NULL, -1);
932 g_return_val_if_fail(item != NULL, -1);
934 session = imap_session_get(folder);
935 if (!session) return -1;
937 ok = imap_select(session, IMAP_FOLDER(folder), item->path,
938 &exists, &recent, &unseen, &uid_validity);
939 if (ok != IMAP_SUCCESS)
944 imap_gen_send(session,
945 "STORE 1:%d +FLAGS (\\Deleted)", exists);
946 ok = imap_cmd_ok(session, NULL);
947 if (ok != IMAP_SUCCESS) {
948 log_warning(_("can't set deleted flags: 1:%d\n"), exists);
952 ok = imap_cmd_expunge(session);
953 if (ok != IMAP_SUCCESS) {
954 log_warning(_("can't expunge\n"));
958 dir = folder_item_get_path(item);
959 if (is_dir_exist(dir))
960 remove_all_numbered_files(dir);
966 gboolean imap_is_msg_changed(Folder *folder, FolderItem *item, MsgInfo *msginfo)
968 /* TODO: properly implement this method */
972 void imap_scan_tree(Folder *folder)
975 IMAPSession *session;
976 gchar *root_folder = NULL;
978 g_return_if_fail(folder != NULL);
979 g_return_if_fail(folder->account != NULL);
981 session = imap_session_get(folder);
984 folder_tree_destroy(folder);
985 item = folder_item_new(folder, folder->name, NULL);
986 item->folder = folder;
987 folder->node = g_node_new(item);
992 if (folder->account->imap_dir && *folder->account->imap_dir) {
993 Xstrdup_a(root_folder, folder->account->imap_dir, return);
994 strtailchomp(root_folder, '/');
995 debug_print("IMAP root directory: %s\n", root_folder);
998 item = folder_item_new(folder, folder->name, root_folder);
999 item->folder = folder;
1000 item->no_select = TRUE;
1001 folder->node = g_node_new(item);
1003 imap_scan_tree_recursive(session, item);
1005 imap_create_missing_folders(folder);
1008 static gint imap_scan_tree_recursive(IMAPSession *session, FolderItem *item)
1011 IMAPFolder *imapfolder;
1012 FolderItem *new_item;
1013 GSList *item_list, *cur;
1015 gchar *wildcard_path;
1019 g_return_val_if_fail(item != NULL, -1);
1020 g_return_val_if_fail(item->folder != NULL, -1);
1021 g_return_val_if_fail(item->no_sub == FALSE, -1);
1023 folder = FOLDER(item->folder);
1024 imapfolder = IMAP_FOLDER(folder);
1026 separator = imap_get_path_separator(imapfolder, item->path);
1028 if (item->folder->ui_func)
1029 item->folder->ui_func(folder, item, folder->ui_func_data);
1032 wildcard[0] = separator;
1035 real_path = imap_get_real_path(imapfolder, item->path);
1039 real_path = g_strdup("");
1042 Xstrcat_a(wildcard_path, real_path, wildcard,
1043 {g_free(real_path); return IMAP_ERROR;});
1044 QUOTE_IF_REQUIRED(wildcard_path, wildcard_path);
1046 imap_gen_send(session, "LIST \"\" %s",
1049 strtailchomp(real_path, separator);
1050 item_list = imap_parse_list(imapfolder, session, real_path, NULL);
1053 for (cur = item_list; cur != NULL; cur = cur->next) {
1054 new_item = cur->data;
1055 if (!strcmp(new_item->path, "INBOX")) {
1056 if (!folder->inbox) {
1057 new_item->stype = F_INBOX;
1058 item->folder->inbox = new_item;
1060 folder_item_destroy(new_item);
1063 } else if (!item->parent || item->stype == F_INBOX) {
1066 base = g_basename(new_item->path);
1068 if (!folder->outbox && !strcasecmp(base, "Sent")) {
1069 new_item->stype = F_OUTBOX;
1070 folder->outbox = new_item;
1071 } else if (!folder->draft && !strcasecmp(base, "Drafts")) {
1072 new_item->stype = F_DRAFT;
1073 folder->draft = new_item;
1074 } else if (!folder->queue && !strcasecmp(base, "Queue")) {
1075 new_item->stype = F_QUEUE;
1076 folder->queue = new_item;
1077 } else if (!folder->trash && !strcasecmp(base, "Trash")) {
1078 new_item->stype = F_TRASH;
1079 folder->trash = new_item;
1082 folder_item_append(item, new_item);
1083 if (new_item->no_sub == FALSE)
1084 imap_scan_tree_recursive(session, new_item);
1087 return IMAP_SUCCESS;
1090 static GSList *imap_parse_list(IMAPFolder *folder, IMAPSession *session,
1091 const gchar *real_path, gchar *separator)
1093 gchar buf[IMAPBUFSIZE];
1095 gchar separator_str[16];
1098 gchar *loc_name, *loc_path;
1099 GSList *item_list = NULL;
1101 FolderItem *new_item;
1103 debug_print("getting list of %s ...\n",
1104 *real_path ? real_path : "\"\"");
1106 str = g_string_new(NULL);
1109 if (sock_gets(SESSION(session)->sock, buf, sizeof(buf)) <= 0) {
1110 log_warning(_("error occurred while getting LIST.\n"));
1114 if (buf[0] != '*' || buf[1] != ' ') {
1115 log_print("IMAP4< %s\n", buf);
1118 debug_print("IMAP4< %s\n", buf);
1120 g_string_assign(str, buf);
1122 if (strncmp(p, "LIST ", 5) != 0) continue;
1125 if (*p != '(') continue;
1127 p = strchr_cpy(p, ')', flags, sizeof(flags));
1129 while (*p == ' ') p++;
1131 p = strchr_cpy(p, ' ', separator_str, sizeof(separator_str));
1133 extract_quote(separator_str, '"');
1134 if (!strcmp(separator_str, "NIL"))
1135 separator_str[0] = '\0';
1137 *separator = separator_str[0];
1140 while (*p == ' ') p++;
1141 if (*p == '{' || *p == '"')
1142 p = imap_parse_atom(SESSION(session)->sock, p,
1143 buf, sizeof(buf), str);
1145 strncpy2(buf, p, sizeof(buf));
1146 strtailchomp(buf, separator_str[0]);
1147 if (buf[0] == '\0') continue;
1148 if (!strcmp(buf, real_path)) continue;
1150 if (separator_str[0] != '\0')
1151 subst_char(buf, separator_str[0], '/');
1152 name = g_basename(buf);
1153 if (name[0] == '.') continue;
1155 loc_name = imap_modified_utf7_to_locale(name);
1156 loc_path = imap_modified_utf7_to_locale(buf);
1157 new_item = folder_item_new(FOLDER(folder), loc_name, loc_path);
1158 if (strcasestr(flags, "\\Noinferiors") != NULL)
1159 new_item->no_sub = TRUE;
1160 if (strcmp(buf, "INBOX") != 0 &&
1161 strcasestr(flags, "\\Noselect") != NULL)
1162 new_item->no_select = TRUE;
1164 item_list = g_slist_append(item_list, new_item);
1166 debug_print("folder %s has been added.\n", loc_path);
1171 g_string_free(str, TRUE);
1176 gint imap_create_tree(Folder *folder)
1178 g_return_val_if_fail(folder != NULL, -1);
1179 g_return_val_if_fail(folder->node != NULL, -1);
1180 g_return_val_if_fail(folder->node->data != NULL, -1);
1181 g_return_val_if_fail(folder->account != NULL, -1);
1183 imap_scan_tree(folder);
1184 imap_create_missing_folders(folder);
1189 static void imap_create_missing_folders(Folder *folder)
1191 g_return_if_fail(folder != NULL);
1194 folder->inbox = imap_create_special_folder
1195 (folder, F_INBOX, "INBOX");
1197 if (!folder->outbox)
1198 folder->outbox = imap_create_special_folder
1199 (folder, F_OUTBOX, "Sent");
1201 folder->draft = imap_create_special_folder
1202 (folder, F_DRAFT, "Drafts");
1204 folder->queue = imap_create_special_folder
1205 (folder, F_QUEUE, "Queue");
1208 folder->trash = imap_create_special_folder
1209 (folder, F_TRASH, "Trash");
1212 static FolderItem *imap_create_special_folder(Folder *folder,
1213 SpecialFolderItemType stype,
1217 FolderItem *new_item;
1219 g_return_val_if_fail(folder != NULL, NULL);
1220 g_return_val_if_fail(folder->node != NULL, NULL);
1221 g_return_val_if_fail(folder->node->data != NULL, NULL);
1222 g_return_val_if_fail(folder->account != NULL, NULL);
1223 g_return_val_if_fail(name != NULL, NULL);
1225 item = FOLDER_ITEM(folder->node->data);
1226 new_item = imap_create_folder(folder, item, name);
1229 g_warning("Can't create '%s'\n", name);
1230 if (!folder->inbox) return NULL;
1232 new_item = imap_create_folder(folder, folder->inbox, name);
1234 g_warning("Can't create '%s' under INBOX\n", name);
1236 new_item->stype = stype;
1238 new_item->stype = stype;
1243 FolderItem *imap_create_folder(Folder *folder, FolderItem *parent,
1246 gchar *dirpath, *imap_path;
1247 IMAPSession *session;
1248 FolderItem *new_item;
1254 g_return_val_if_fail(folder != NULL, NULL);
1255 g_return_val_if_fail(folder->account != NULL, NULL);
1256 g_return_val_if_fail(parent != NULL, NULL);
1257 g_return_val_if_fail(name != NULL, NULL);
1259 session = imap_session_get(folder);
1260 if (!session) return NULL;
1262 if (!parent->parent && strcmp(name, "INBOX") == 0)
1263 dirpath = g_strdup(name);
1264 else if (parent->path)
1265 dirpath = g_strconcat(parent->path, "/", name, NULL);
1266 else if ((p = strchr(name, '/')) != NULL && *(p + 1) != '\0')
1267 dirpath = g_strdup(name);
1268 else if (folder->account->imap_dir && *folder->account->imap_dir) {
1271 Xstrdup_a(imap_dir, folder->account->imap_dir, return NULL);
1272 strtailchomp(imap_dir, '/');
1273 dirpath = g_strconcat(imap_dir, "/", name, NULL);
1275 dirpath = g_strdup(name);
1277 /* keep trailing directory separator to create a folder that contains
1279 imap_path = imap_locale_to_modified_utf7(dirpath);
1280 strtailchomp(dirpath, '/');
1281 Xstrdup_a(new_name, name, {g_free(dirpath); return NULL;});
1282 strtailchomp(new_name, '/');
1283 separator = imap_get_path_separator(IMAP_FOLDER(folder), imap_path);
1284 imap_path_separator_subst(imap_path, separator);
1285 subst_char(new_name, '/', separator);
1287 if (strcmp(name, "INBOX") != 0) {
1290 gboolean exist = FALSE;
1292 argbuf = g_ptr_array_new();
1293 ok = imap_cmd_list(session, NULL, imap_path,
1295 if (ok != IMAP_SUCCESS) {
1296 log_warning(_("can't create mailbox: LIST failed\n"));
1299 ptr_array_free_strings(argbuf);
1300 g_ptr_array_free(argbuf, TRUE);
1304 for (i = 0; i < argbuf->len; i++) {
1306 str = g_ptr_array_index(argbuf, i);
1307 if (!strncmp(str, "LIST ", 5)) {
1312 ptr_array_free_strings(argbuf);
1313 g_ptr_array_free(argbuf, TRUE);
1316 ok = imap_cmd_create(session, imap_path);
1317 if (ok != IMAP_SUCCESS) {
1318 log_warning(_("can't create mailbox\n"));
1326 new_item = folder_item_new(folder, new_name, dirpath);
1327 folder_item_append(parent, new_item);
1331 dirpath = folder_item_get_path(new_item);
1332 if (!is_dir_exist(dirpath))
1333 make_dir_hier(dirpath);
1339 gint imap_rename_folder(Folder *folder, FolderItem *item, const gchar *name)
1343 gchar *real_oldpath;
1344 gchar *real_newpath;
1347 gchar *old_cache_dir;
1348 gchar *new_cache_dir;
1349 IMAPSession *session;
1352 gint exists, recent, unseen;
1353 guint32 uid_validity;
1355 g_return_val_if_fail(folder != NULL, -1);
1356 g_return_val_if_fail(item != NULL, -1);
1357 g_return_val_if_fail(item->path != NULL, -1);
1358 g_return_val_if_fail(name != NULL, -1);
1360 session = imap_session_get(folder);
1361 if (!session) return -1;
1363 real_oldpath = imap_get_real_path(IMAP_FOLDER(folder), item->path);
1365 g_free(session->mbox);
1366 session->mbox = NULL;
1367 ok = imap_cmd_examine(session, "INBOX",
1368 &exists, &recent, &unseen, &uid_validity);
1369 if (ok != IMAP_SUCCESS) {
1370 g_free(real_oldpath);
1374 separator = imap_get_path_separator(IMAP_FOLDER(folder), item->path);
1375 if (strchr(item->path, G_DIR_SEPARATOR)) {
1376 dirpath = g_dirname(item->path);
1377 newpath = g_strconcat(dirpath, G_DIR_SEPARATOR_S, name, NULL);
1380 newpath = g_strdup(name);
1382 real_newpath = imap_locale_to_modified_utf7(newpath);
1383 imap_path_separator_subst(real_newpath, separator);
1385 ok = imap_cmd_rename(session, real_oldpath, real_newpath);
1386 if (ok != IMAP_SUCCESS) {
1387 log_warning(_("can't rename mailbox: %s to %s\n"),
1388 real_oldpath, real_newpath);
1389 g_free(real_oldpath);
1391 g_free(real_newpath);
1396 item->name = g_strdup(name);
1398 old_cache_dir = folder_item_get_path(item);
1400 node = g_node_find(item->folder->node, G_PRE_ORDER, G_TRAVERSE_ALL,
1402 paths[0] = g_strdup(item->path);
1404 g_node_traverse(node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
1405 imap_rename_folder_func, paths);
1407 if (is_dir_exist(old_cache_dir)) {
1408 new_cache_dir = folder_item_get_path(item);
1409 if (rename(old_cache_dir, new_cache_dir) < 0) {
1410 FILE_OP_ERROR(old_cache_dir, "rename");
1412 g_free(new_cache_dir);
1415 g_free(old_cache_dir);
1418 g_free(real_oldpath);
1419 g_free(real_newpath);
1424 gint imap_remove_folder(Folder *folder, FolderItem *item)
1427 IMAPSession *session;
1430 gint exists, recent, unseen;
1431 guint32 uid_validity;
1433 g_return_val_if_fail(folder != NULL, -1);
1434 g_return_val_if_fail(item != NULL, -1);
1435 g_return_val_if_fail(item->path != NULL, -1);
1437 session = imap_session_get(folder);
1438 if (!session) return -1;
1440 path = imap_get_real_path(IMAP_FOLDER(folder), item->path);
1442 ok = imap_cmd_examine(session, "INBOX",
1443 &exists, &recent, &unseen, &uid_validity);
1444 if (ok != IMAP_SUCCESS) {
1449 ok = imap_cmd_delete(session, path);
1450 if (ok != IMAP_SUCCESS) {
1451 log_warning(_("can't delete mailbox\n"));
1457 cache_dir = folder_item_get_path(item);
1458 if (is_dir_exist(cache_dir) && remove_dir_recursive(cache_dir) < 0)
1459 g_warning("can't remove directory '%s'\n", cache_dir);
1461 folder_item_remove(item);
1466 static GSList *imap_get_uncached_messages(IMAPSession *session,
1468 MsgNumberList *numlist)
1471 GSList *newlist = NULL;
1472 GSList *llast = NULL;
1477 g_return_val_if_fail(session != NULL, NULL);
1478 g_return_val_if_fail(item != NULL, NULL);
1479 g_return_val_if_fail(item->folder != NULL, NULL);
1480 g_return_val_if_fail(FOLDER_CLASS(item->folder) == &imap_class, NULL);
1482 imapset = numberlist_to_imapset(numlist);
1483 while (imapset != NULL) {
1484 if (imap_cmd_envelope(session, imapset)
1486 log_warning(_("can't get envelope\n"));
1490 str = g_string_new(NULL);
1493 if ((tmp = sock_getline(SESSION(session)->sock)) == NULL) {
1494 log_warning(_("error occurred while getting envelope.\n"));
1495 g_string_free(str, TRUE);
1499 if (tmp[0] != '*' || tmp[1] != ' ') {
1500 log_print("IMAP4< %s\n", tmp);
1504 if (strstr(tmp, "FETCH") == NULL) {
1505 log_print("IMAP4< %s\n", tmp);
1509 log_print("IMAP4< %s\n", tmp);
1510 g_string_assign(str, tmp);
1513 msginfo = imap_parse_envelope
1514 (SESSION(session)->sock, item, str);
1516 log_warning(_("can't parse envelope: %s\n"), str->str);
1519 if (item->stype == F_QUEUE) {
1520 MSG_SET_TMP_FLAGS(msginfo->flags, MSG_QUEUED);
1521 } else if (item->stype == F_DRAFT) {
1522 MSG_SET_TMP_FLAGS(msginfo->flags, MSG_DRAFT);
1525 msginfo->folder = item;
1528 llast = newlist = g_slist_append(newlist, msginfo);
1530 llast = g_slist_append(llast, msginfo);
1531 llast = llast->next;
1535 g_string_free(str, TRUE);
1536 imapset = numberlist_to_imapset(NULL);
1542 static void imap_delete_all_cached_messages(FolderItem *item)
1546 g_return_if_fail(item != NULL);
1547 g_return_if_fail(item->folder != NULL);
1548 g_return_if_fail(FOLDER_CLASS(item->folder) == &imap_class);
1550 debug_print("Deleting all cached messages...\n");
1552 dir = folder_item_get_path(item);
1553 if (is_dir_exist(dir))
1554 remove_all_numbered_files(dir);
1557 debug_print("done.\n");
1561 static SockInfo *imap_open_tunnel(const gchar *server,
1562 const gchar *tunnelcmd,
1565 static SockInfo *imap_open_tunnel(const gchar *server,
1566 const gchar *tunnelcmd)
1571 if ((sock = sock_connect_cmd(server, tunnelcmd)) == NULL) {
1572 log_warning(_("Can't establish IMAP4 session with: %s\n"),
1577 return imap_init_sock(sock, ssl_type);
1579 return imap_init_sock(sock);
1585 static SockInfo *imap_open(const gchar *server, gushort port,
1588 static SockInfo *imap_open(const gchar *server, gushort port)
1593 if ((sock = sock_connect(server, port)) == NULL) {
1594 log_warning(_("Can't connect to IMAP4 server: %s:%d\n"),
1600 if (ssl_type == SSL_TUNNEL && !ssl_init_socket(sock)) {
1601 log_warning(_("Can't establish IMAP4 session with: %s:%d\n"),
1611 static SockInfo *imap_init_sock(SockInfo *sock, SSLType ssl_type)
1613 static SockInfo *imap_init_sock(SockInfo *sock)
1620 static GList *imap_parse_namespace_str(gchar *str)
1625 IMAPNameSpace *namespace;
1626 GList *ns_list = NULL;
1628 while (*p != '\0') {
1629 /* parse ("#foo" "/") */
1631 while (*p && *p != '(') p++;
1632 if (*p == '\0') break;
1635 while (*p && *p != '"') p++;
1636 if (*p == '\0') break;
1640 while (*p && *p != '"') p++;
1641 if (*p == '\0') break;
1645 while (*p && isspace(*p)) p++;
1646 if (*p == '\0') break;
1647 if (strncmp(p, "NIL", 3) == 0)
1649 else if (*p == '"') {
1652 while (*p && *p != '"') p++;
1653 if (*p == '\0') break;
1658 while (*p && *p != ')') p++;
1659 if (*p == '\0') break;
1662 namespace = g_new(IMAPNameSpace, 1);
1663 namespace->name = g_strdup(name);
1664 namespace->separator = separator ? separator[0] : '\0';
1665 ns_list = g_list_append(ns_list, namespace);
1671 static void imap_parse_namespace(IMAPSession *session, IMAPFolder *folder)
1676 g_return_if_fail(session != NULL);
1677 g_return_if_fail(folder != NULL);
1679 if (folder->ns_personal != NULL ||
1680 folder->ns_others != NULL ||
1681 folder->ns_shared != NULL)
1684 if (!imap_has_capability(session, "NAMESPACE")) {
1685 imap_get_namespace_by_list(session, folder);
1689 if (imap_cmd_namespace(session, &ns_str)
1691 log_warning(_("can't get namespace\n"));
1695 str_array = strsplit_parenthesis(ns_str, '(', ')', 3);
1696 if (str_array == NULL) {
1698 imap_get_namespace_by_list(session, folder);
1702 folder->ns_personal = imap_parse_namespace_str(str_array[0]);
1703 if (str_array[0] && str_array[1])
1704 folder->ns_others = imap_parse_namespace_str(str_array[1]);
1705 if (str_array[0] && str_array[1] && str_array[2])
1706 folder->ns_shared = imap_parse_namespace_str(str_array[2]);
1707 g_strfreev(str_array);
1711 static void imap_get_namespace_by_list(IMAPSession *session, IMAPFolder *folder)
1713 GSList *item_list, *cur;
1714 gchar separator = '\0';
1715 IMAPNameSpace *namespace;
1717 g_return_if_fail(session != NULL);
1718 g_return_if_fail(folder != NULL);
1720 if (folder->ns_personal != NULL ||
1721 folder->ns_others != NULL ||
1722 folder->ns_shared != NULL)
1725 imap_gen_send(session, "LIST \"\" \"\"");
1726 item_list = imap_parse_list(folder, session, "", &separator);
1727 for (cur = item_list; cur != NULL; cur = cur->next)
1728 folder_item_destroy(FOLDER_ITEM(cur->data));
1729 g_slist_free(item_list);
1731 namespace = g_new(IMAPNameSpace, 1);
1732 namespace->name = g_strdup("");
1733 namespace->separator = separator;
1734 folder->ns_personal = g_list_append(NULL, namespace);
1737 static IMAPNameSpace *imap_find_namespace_from_list(GList *ns_list,
1740 IMAPNameSpace *namespace = NULL;
1741 gchar *tmp_path, *name;
1743 if (!path) path = "";
1745 Xstrcat_a(tmp_path, path, "/", return NULL);
1747 for (; ns_list != NULL; ns_list = ns_list->next) {
1748 IMAPNameSpace *tmp_ns = ns_list->data;
1750 Xstrdup_a(name, tmp_ns->name, return namespace);
1751 if (tmp_ns->separator && tmp_ns->separator != '/')
1752 subst_char(name, tmp_ns->separator, '/');
1753 if (strncmp(tmp_path, name, strlen(name)) == 0)
1760 static IMAPNameSpace *imap_find_namespace(IMAPFolder *folder,
1763 IMAPNameSpace *namespace;
1765 g_return_val_if_fail(folder != NULL, NULL);
1767 namespace = imap_find_namespace_from_list(folder->ns_personal, path);
1768 if (namespace) return namespace;
1769 namespace = imap_find_namespace_from_list(folder->ns_others, path);
1770 if (namespace) return namespace;
1771 namespace = imap_find_namespace_from_list(folder->ns_shared, path);
1772 if (namespace) return namespace;
1777 static gchar imap_get_path_separator(IMAPFolder *folder, const gchar *path)
1779 IMAPNameSpace *namespace;
1780 gchar separator = '/';
1782 namespace = imap_find_namespace(folder, path);
1783 if (namespace && namespace->separator)
1784 separator = namespace->separator;
1789 static gchar *imap_get_real_path(IMAPFolder *folder, const gchar *path)
1794 g_return_val_if_fail(folder != NULL, NULL);
1795 g_return_val_if_fail(path != NULL, NULL);
1797 real_path = imap_locale_to_modified_utf7(path);
1798 separator = imap_get_path_separator(folder, path);
1799 imap_path_separator_subst(real_path, separator);
1804 static gchar *imap_parse_atom(SockInfo *sock, gchar *src,
1805 gchar *dest, gint dest_len, GString *str)
1807 gchar *cur_pos = src;
1810 g_return_val_if_fail(str != NULL, cur_pos);
1812 /* read the next line if the current response buffer is empty */
1813 while (isspace(*cur_pos)) cur_pos++;
1814 while (*cur_pos == '\0') {
1815 if ((nextline = sock_getline(sock)) == NULL)
1817 g_string_assign(str, nextline);
1819 strretchomp(nextline);
1820 /* log_print("IMAP4< %s\n", nextline); */
1821 debug_print("IMAP4< %s\n", nextline);
1824 while (isspace(*cur_pos)) cur_pos++;
1827 if (!strncmp(cur_pos, "NIL", 3)) {
1830 } else if (*cur_pos == '\"') {
1833 p = get_quoted(cur_pos, '\"', dest, dest_len);
1834 cur_pos = p ? p : cur_pos + 2;
1835 } else if (*cur_pos == '{') {
1840 cur_pos = strchr_cpy(cur_pos + 1, '}', buf, sizeof(buf));
1842 g_return_val_if_fail(len > 0, cur_pos);
1844 g_string_truncate(str, 0);
1848 if ((nextline = sock_getline(sock)) == NULL)
1850 line_len += strlen(nextline);
1851 g_string_append(str, nextline);
1853 strretchomp(nextline);
1854 /* log_print("IMAP4< %s\n", nextline); */
1855 debug_print("IMAP4< %s\n", nextline);
1857 } while (line_len < len);
1859 memcpy(dest, cur_pos, MIN(len, dest_len - 1));
1860 dest[MIN(len, dest_len - 1)] = '\0';
1867 static gchar *imap_get_header(SockInfo *sock, gchar *cur_pos, gchar **headers,
1877 g_return_val_if_fail(str != NULL, cur_pos);
1879 while (isspace(*cur_pos)) cur_pos++;
1881 g_return_val_if_fail(*cur_pos == '{', cur_pos);
1883 cur_pos = strchr_cpy(cur_pos + 1, '}', buf, sizeof(buf));
1885 g_return_val_if_fail(len > 0, cur_pos);
1887 g_string_truncate(str, 0);
1891 if ((nextline = sock_getline(sock)) == NULL)
1893 block_len += strlen(nextline);
1894 g_string_append(str, nextline);
1896 strretchomp(nextline);
1897 /* debug_print("IMAP4< %s\n", nextline); */
1899 } while (block_len < len);
1901 debug_print("IMAP4< [contents of BODY.PEEK[HEADER.FIELDS (...)]]\n");
1903 *headers = g_strndup(cur_pos, len);
1906 while (isspace(*cur_pos)) cur_pos++;
1907 while (*cur_pos == '\0') {
1908 if ((nextline = sock_getline(sock)) == NULL)
1910 g_string_assign(str, nextline);
1912 strretchomp(nextline);
1913 debug_print("IMAP4< %s\n", nextline);
1916 while (isspace(*cur_pos)) cur_pos++;
1922 static MsgFlags imap_parse_flags(const gchar *flag_str)
1924 const gchar *p = flag_str;
1925 MsgFlags flags = {0, 0};
1927 flags.perm_flags = MSG_UNREAD;
1929 while ((p = strchr(p, '\\')) != NULL) {
1932 if (g_strncasecmp(p, "Recent", 6) == 0 && MSG_IS_UNREAD(flags)) {
1933 MSG_SET_PERM_FLAGS(flags, MSG_NEW);
1934 } else if (g_strncasecmp(p, "Seen", 4) == 0) {
1935 MSG_UNSET_PERM_FLAGS(flags, MSG_NEW|MSG_UNREAD);
1936 } else if (g_strncasecmp(p, "Deleted", 7) == 0) {
1937 MSG_SET_PERM_FLAGS(flags, MSG_DELETED);
1938 } else if (g_strncasecmp(p, "Flagged", 7) == 0) {
1939 MSG_SET_PERM_FLAGS(flags, MSG_MARKED);
1940 } else if (g_strncasecmp(p, "Answered", 8) == 0) {
1941 MSG_SET_PERM_FLAGS(flags, MSG_REPLIED);
1948 static MsgInfo *imap_parse_envelope(SockInfo *sock, FolderItem *item,
1951 gchar buf[IMAPBUFSIZE];
1952 MsgInfo *msginfo = NULL;
1957 MsgFlags flags = {0, 0}, imap_flags = {0, 0};
1959 g_return_val_if_fail(line_str != NULL, NULL);
1960 g_return_val_if_fail(line_str->str[0] == '*' &&
1961 line_str->str[1] == ' ', NULL);
1963 MSG_SET_TMP_FLAGS(flags, MSG_IMAP);
1964 if (item->stype == F_QUEUE) {
1965 MSG_SET_TMP_FLAGS(flags, MSG_QUEUED);
1966 } else if (item->stype == F_DRAFT) {
1967 MSG_SET_TMP_FLAGS(flags, MSG_DRAFT);
1970 cur_pos = line_str->str + 2;
1972 #define PARSE_ONE_ELEMENT(ch) \
1974 cur_pos = strchr_cpy(cur_pos, ch, buf, sizeof(buf)); \
1975 if (cur_pos == NULL) { \
1976 g_warning("cur_pos == NULL\n"); \
1977 procmsg_msginfo_free(msginfo); \
1982 PARSE_ONE_ELEMENT(' ');
1985 PARSE_ONE_ELEMENT(' ');
1986 g_return_val_if_fail(!strcmp(buf, "FETCH"), NULL);
1988 g_return_val_if_fail(*cur_pos == '(', NULL);
1991 while (*cur_pos != '\0' && *cur_pos != ')') {
1992 while (*cur_pos == ' ') cur_pos++;
1994 if (!strncmp(cur_pos, "UID ", 4)) {
1996 uid = strtoul(cur_pos, &cur_pos, 10);
1997 } else if (!strncmp(cur_pos, "FLAGS ", 6)) {
1999 if (*cur_pos != '(') {
2000 g_warning("*cur_pos != '('\n");
2001 procmsg_msginfo_free(msginfo);
2005 PARSE_ONE_ELEMENT(')');
2006 imap_flags = imap_parse_flags(buf);
2007 } else if (!strncmp(cur_pos, "RFC822.SIZE ", 12)) {
2009 size = strtol(cur_pos, &cur_pos, 10);
2010 } else if (!strncmp(cur_pos, "BODY[HEADER.FIELDS ", 19)) {
2014 if (*cur_pos != '(') {
2015 g_warning("*cur_pos != '('\n");
2016 procmsg_msginfo_free(msginfo);
2020 PARSE_ONE_ELEMENT(')');
2021 if (*cur_pos != ']') {
2022 g_warning("*cur_pos != ']'\n");
2023 procmsg_msginfo_free(msginfo);
2028 cur_pos = imap_get_header(sock, cur_pos, &headers,
2030 msginfo = procheader_parse_str(headers, flags, FALSE, FALSE);
2033 g_warning("invalid FETCH response: %s\n", cur_pos);
2039 msginfo->msgnum = uid;
2040 msginfo->size = size;
2041 msginfo->flags.tmp_flags |= imap_flags.tmp_flags;
2042 msginfo->flags.perm_flags = imap_flags.perm_flags;
2048 static gint imap_set_message_flags(IMAPSession *session,
2049 MsgNumberList *numlist,
2057 buf = g_string_new(is_set ? "+FLAGS (" : "-FLAGS (");
2059 if (IMAP_IS_SEEN(flags)) g_string_append(buf, "\\Seen ");
2060 if (IMAP_IS_ANSWERED(flags)) g_string_append(buf, "\\Answered ");
2061 if (IMAP_IS_FLAGGED(flags)) g_string_append(buf, "\\Flagged ");
2062 if (IMAP_IS_DELETED(flags)) g_string_append(buf, "\\Deleted ");
2063 if (IMAP_IS_DRAFT(flags)) g_string_append(buf, "\\Draft");
2065 if (buf->str[buf->len - 1] == ' ')
2066 g_string_truncate(buf, buf->len - 1);
2068 g_string_append_c(buf, ')');
2070 imapset = numberlist_to_imapset(numlist);
2071 while (imapset != NULL) {
2072 ok = imap_cmd_store(session, imapset,
2074 imapset = numberlist_to_imapset(NULL);
2076 g_string_free(buf, TRUE);
2081 static gint imap_select(IMAPSession *session, IMAPFolder *folder,
2083 gint *exists, gint *recent, gint *unseen,
2084 guint32 *uid_validity)
2088 gint exists_, recent_, unseen_, uid_validity_;
2090 if (!exists || !recent || !unseen || !uid_validity) {
2091 if (session->mbox && strcmp(session->mbox, path) == 0)
2092 return IMAP_SUCCESS;
2096 uid_validity = &uid_validity_;
2099 g_free(session->mbox);
2100 session->mbox = NULL;
2102 real_path = imap_get_real_path(folder, path);
2103 ok = imap_cmd_select(session, real_path,
2104 exists, recent, unseen, uid_validity);
2105 if (ok != IMAP_SUCCESS)
2106 log_warning(_("can't select folder: %s\n"), real_path);
2108 session->mbox = g_strdup(path);
2109 session->folder_content_changed = FALSE;
2116 #define THROW(err) { ok = err; goto catch; }
2118 static gint imap_status(IMAPSession *session, IMAPFolder *folder,
2120 gint *messages, gint *recent,
2121 guint32 *uid_next, guint32 *uid_validity,
2130 *messages = *recent = *uid_next = *uid_validity = *unseen = 0;
2132 argbuf = g_ptr_array_new();
2134 real_path = imap_get_real_path(folder, path);
2135 QUOTE_IF_REQUIRED(real_path_, real_path);
2136 imap_gen_send(session, "STATUS %s "
2137 "(MESSAGES RECENT UIDNEXT UIDVALIDITY UNSEEN)",
2140 ok = imap_cmd_ok(session, argbuf);
2141 if (ok != IMAP_SUCCESS) THROW(ok);
2143 str = search_array_str(argbuf, "STATUS");
2144 if (!str) THROW(IMAP_ERROR);
2146 str = strchr(str, '(');
2147 if (!str) THROW(IMAP_ERROR);
2149 while (*str != '\0' && *str != ')') {
2150 while (*str == ' ') str++;
2152 if (!strncmp(str, "MESSAGES ", 9)) {
2154 *messages = strtol(str, &str, 10);
2155 } else if (!strncmp(str, "RECENT ", 7)) {
2157 *recent = strtol(str, &str, 10);
2158 } else if (!strncmp(str, "UIDNEXT ", 8)) {
2160 *uid_next = strtoul(str, &str, 10);
2161 } else if (!strncmp(str, "UIDVALIDITY ", 12)) {
2163 *uid_validity = strtoul(str, &str, 10);
2164 } else if (!strncmp(str, "UNSEEN ", 7)) {
2166 *unseen = strtol(str, &str, 10);
2168 g_warning("invalid STATUS response: %s\n", str);
2175 ptr_array_free_strings(argbuf);
2176 g_ptr_array_free(argbuf, TRUE);
2184 /* low-level IMAP4rev1 commands */
2186 static gint imap_cmd_login(IMAPSession *session,
2187 const gchar *user, const gchar *pass)
2189 gchar *user_, *pass_;
2192 QUOTE_IF_REQUIRED(user_, user);
2193 QUOTE_IF_REQUIRED(pass_, pass);
2194 imap_gen_send(session, "LOGIN %s %s", user_, pass_);
2196 ok = imap_cmd_ok(session, NULL);
2197 if (ok != IMAP_SUCCESS)
2198 log_warning(_("IMAP4 login failed.\n"));
2203 static gint imap_cmd_logout(IMAPSession *session)
2205 imap_gen_send(session, "LOGOUT");
2206 return imap_cmd_ok(session, NULL);
2209 /* Send CAPABILITY, and examine the server's response to see whether this
2210 * connection is pre-authenticated or not and build a list of CAPABILITIES. */
2211 static gint imap_greeting(IMAPSession *session)
2216 imap_gen_send(session, "CAPABILITY");
2218 argbuf = g_ptr_array_new();
2220 if (imap_cmd_ok(session, argbuf) != IMAP_SUCCESS ||
2221 ((capstr = search_array_str(argbuf, "CAPABILITY ")) == NULL)) {
2222 ptr_array_free_strings(argbuf);
2223 g_ptr_array_free(argbuf, TRUE);
2227 session->authenticated = search_array_str(argbuf, "PREAUTH") != NULL;
2229 capstr += strlen("CAPABILITY ");
2231 IMAP_SESSION(session)->capability = g_strsplit(capstr, " ", 0);
2233 ptr_array_free_strings(argbuf);
2234 g_ptr_array_free(argbuf, TRUE);
2239 static gboolean imap_has_capability(IMAPSession *session, const gchar *cap)
2243 for (p = session->capability; *p != NULL; ++p)
2244 if (g_strcasecmp(*p, cap) == 0)
2250 void imap_free_capabilities(IMAPSession *session)
2252 g_strfreev(session->capability);
2253 session->capability = NULL;
2256 static const IMAPSet numberlist_to_imapset(MsgNumberList *list)
2258 static GString *imapset = NULL;
2259 static MsgNumberList *numlist, *elem;
2260 guint first, last, next;
2262 if (imapset == NULL)
2263 imapset = g_string_sized_new(256);
2265 g_string_truncate(imapset, 0);
2268 g_slist_free(numlist);
2269 numlist = g_slist_copy(list);
2270 numlist = g_slist_sort(numlist, g_int_compare);
2271 } else if (numlist == NULL) {
2275 first = GPOINTER_TO_INT(numlist->data);
2277 for(elem = g_slist_next(numlist); elem != NULL; elem = g_slist_next(elem)) {
2278 next = GPOINTER_TO_INT(elem->data);
2280 if(next != (last + 1)) {
2281 if (imapset->len > 0)
2282 g_string_append(imapset, ",");
2284 g_string_sprintfa(imapset, "%d", first);
2286 g_string_sprintfa(imapset, "%d:%d", first, last);
2288 if (imapset->len > IMAPCMDLIMIT) {
2298 if (imapset->len > 0)
2299 g_string_append(imapset, ",");
2301 g_string_sprintfa(imapset, "%d", first);
2303 g_string_sprintfa(imapset, "%d:%d", first, last);
2305 g_slist_free(numlist);
2308 MsgNumberList *remaining;
2310 remaining = elem->next;
2311 remaining = g_slist_prepend(remaining, elem->data);
2313 g_slist_free(numlist);
2314 numlist = remaining;
2317 return imapset->str;
2320 static gint imap_cmd_noop(IMAPSession *session)
2322 imap_gen_send(session, "NOOP");
2323 return imap_cmd_ok(session, NULL);
2326 static gint imap_cmd_starttls(IMAPSession *session)
2328 imap_gen_send(session, "STARTTLS");
2329 return imap_cmd_ok(session, NULL);
2332 #define THROW(err) { ok = err; goto catch; }
2334 static gint imap_cmd_namespace(IMAPSession *session, gchar **ns_str)
2340 argbuf = g_ptr_array_new();
2342 imap_gen_send(session, "NAMESPACE");
2343 if ((ok = imap_cmd_ok(session, argbuf)) != IMAP_SUCCESS) THROW(ok);
2345 str = search_array_str(argbuf, "NAMESPACE");
2346 if (!str) THROW(IMAP_ERROR);
2348 *ns_str = g_strdup(str);
2351 ptr_array_free_strings(argbuf);
2352 g_ptr_array_free(argbuf, TRUE);
2359 static gint imap_cmd_list(IMAPSession *session, const gchar *ref,
2360 const gchar *mailbox, GPtrArray *argbuf)
2362 gchar *ref_, *mailbox_;
2364 if (!ref) ref = "\"\"";
2365 if (!mailbox) mailbox = "\"\"";
2367 QUOTE_IF_REQUIRED(ref_, ref);
2368 QUOTE_IF_REQUIRED(mailbox_, mailbox);
2369 imap_gen_send(session, "LIST %s %s", ref_, mailbox_);
2371 return imap_cmd_ok(session, argbuf);
2374 #define THROW goto catch
2376 static gint imap_cmd_do_select(IMAPSession *session, const gchar *folder,
2378 gint *exists, gint *recent, gint *unseen,
2379 guint32 *uid_validity)
2387 *exists = *recent = *unseen = *uid_validity = 0;
2388 argbuf = g_ptr_array_new();
2391 select_cmd = "EXAMINE";
2393 select_cmd = "SELECT";
2395 QUOTE_IF_REQUIRED(folder_, folder);
2396 imap_gen_send(session, "%s %s", select_cmd, folder_);
2398 if ((ok = imap_cmd_ok(session, argbuf)) != IMAP_SUCCESS) THROW;
2400 resp_str = search_array_contain_str(argbuf, "EXISTS");
2402 if (sscanf(resp_str,"%d EXISTS", exists) != 1) {
2403 g_warning("imap_cmd_select(): invalid EXISTS line.\n");
2408 resp_str = search_array_contain_str(argbuf, "RECENT");
2410 if (sscanf(resp_str, "%d RECENT", recent) != 1) {
2411 g_warning("imap_cmd_select(): invalid RECENT line.\n");
2416 resp_str = search_array_contain_str(argbuf, "UIDVALIDITY");
2418 if (sscanf(resp_str, "OK [UIDVALIDITY %u] ", uid_validity)
2420 g_warning("imap_cmd_select(): invalid UIDVALIDITY line.\n");
2425 resp_str = search_array_contain_str(argbuf, "UNSEEN");
2427 if (sscanf(resp_str, "OK [UNSEEN %d] ", unseen) != 1) {
2428 g_warning("imap_cmd_select(): invalid UNSEEN line.\n");
2434 ptr_array_free_strings(argbuf);
2435 g_ptr_array_free(argbuf, TRUE);
2440 static gint imap_cmd_select(IMAPSession *session, const gchar *folder,
2441 gint *exists, gint *recent, gint *unseen,
2442 guint32 *uid_validity)
2444 return imap_cmd_do_select(session, folder, FALSE,
2445 exists, recent, unseen, uid_validity);
2448 static gint imap_cmd_examine(IMAPSession *session, const gchar *folder,
2449 gint *exists, gint *recent, gint *unseen,
2450 guint32 *uid_validity)
2452 return imap_cmd_do_select(session, folder, TRUE,
2453 exists, recent, unseen, uid_validity);
2458 static gint imap_cmd_create(IMAPSession *session, const gchar *folder)
2462 QUOTE_IF_REQUIRED(folder_, folder);
2463 imap_gen_send(session, "CREATE %s", folder_);
2465 return imap_cmd_ok(session, NULL);
2468 static gint imap_cmd_rename(IMAPSession *session, const gchar *old_folder,
2469 const gchar *new_folder)
2471 gchar *old_folder_, *new_folder_;
2473 QUOTE_IF_REQUIRED(old_folder_, old_folder);
2474 QUOTE_IF_REQUIRED(new_folder_, new_folder);
2475 imap_gen_send(session, "RENAME %s %s", old_folder_, new_folder_);
2477 return imap_cmd_ok(session, NULL);
2480 static gint imap_cmd_delete(IMAPSession *session, const gchar *folder)
2484 QUOTE_IF_REQUIRED(folder_, folder);
2485 imap_gen_send(session, "DELETE %s", folder_);
2487 return imap_cmd_ok(session, NULL);
2490 static gint imap_cmd_search(IMAPSession *session, const gchar *criteria, GSList **list)
2496 g_return_val_if_fail(criteria != NULL, IMAP_ERROR);
2497 g_return_val_if_fail(list != NULL, IMAP_ERROR);
2501 argbuf = g_ptr_array_new();
2502 imap_gen_send(session, "UID SEARCH %s", criteria);
2504 ok = imap_cmd_ok(session, argbuf);
2505 if (ok != IMAP_SUCCESS) {
2506 ptr_array_free_strings(argbuf);
2507 g_ptr_array_free(argbuf, TRUE);
2511 if ((uidlist = search_array_str(argbuf, "SEARCH ")) != NULL) {
2512 gchar **strlist, **p;
2514 strlist = g_strsplit(uidlist + 7, " ", 0);
2515 for (p = strlist; *p != NULL; ++p) {
2518 if (sscanf(*p, "%d", &msgnum) == 1)
2519 *list = g_slist_append(*list, GINT_TO_POINTER(msgnum));
2521 g_strfreev(strlist);
2523 ptr_array_free_strings(argbuf);
2524 g_ptr_array_free(argbuf, TRUE);
2526 return IMAP_SUCCESS;
2529 static gint imap_cmd_fetch(IMAPSession *session, guint32 uid, const gchar *filename)
2537 g_return_val_if_fail(filename != NULL, IMAP_ERROR);
2539 imap_gen_send(session, "UID FETCH %d BODY.PEEK[]", uid);
2541 while ((ok = imap_gen_recv(session, &buf))
2543 if (buf[0] != '*' || buf[1] != ' ') {
2547 if (strstr(buf, "FETCH") != NULL)
2550 if (ok != IMAP_SUCCESS)
2553 cur_pos = strchr(buf, '{');
2554 if (cur_pos == NULL) {
2558 cur_pos = strchr_cpy(cur_pos + 1, '}', size_str, sizeof(size_str));
2559 if (cur_pos == NULL) {
2563 size_num = atol(size_str);
2564 g_return_val_if_fail(size_num > 0, IMAP_ERROR);
2566 if (*cur_pos != '\0') {
2571 if (recv_bytes_write_to_file(SESSION(session)->sock, size_num, filename) != 0) {
2576 if (imap_gen_recv(session, &buf) != IMAP_SUCCESS) {
2581 if (buf[0] == '\0' || buf[strlen(buf) - 1] != ')') {
2587 ok = imap_cmd_ok(session, NULL);
2592 static gint imap_cmd_append(IMAPSession *session, const gchar *destfolder,
2593 const gchar *file, gint32 *new_uid)
2598 gchar buf[BUFFSIZE], *imapbuf;
2603 g_return_val_if_fail(file != NULL, IMAP_ERROR);
2605 size = get_file_size_as_crlf(file);
2606 if ((fp = fopen(file, "rb")) == NULL) {
2607 FILE_OP_ERROR(file, "fopen");
2610 QUOTE_IF_REQUIRED(destfolder_, destfolder);
2611 imap_gen_send(session, "APPEND %s (\\Seen) {%d}", destfolder_, size);
2613 ok = imap_gen_recv(session, &imapbuf);
2614 if (ok != IMAP_SUCCESS || imapbuf[0] != '+' || imapbuf[1] != ' ') {
2615 log_warning(_("can't append %s to %s\n"), file, destfolder_);
2622 log_print("IMAP4> %s\n", _("(sending file...)"));
2624 while (fgets(buf, sizeof(buf), fp) != NULL) {
2626 if (sock_puts(SESSION(session)->sock, buf) < 0) {
2633 FILE_OP_ERROR(file, "fgets");
2638 sock_puts(SESSION(session)->sock, "");
2642 reply = g_ptr_array_new();
2645 ok = imap_cmd_ok(session, reply);
2646 if (ok != IMAP_SUCCESS)
2647 log_warning(_("can't append message to %s\n"), destfolder_);
2649 (new_uid != NULL) &&
2650 (imap_has_capability(session, "UIDPLUS") && reply->len > 0) &&
2651 ((okmsginfo = g_ptr_array_index(reply, reply->len - 1)) != NULL) &&
2652 (sscanf(okmsginfo, "%*u OK [APPENDUID %*u %u]", &newuid) == 1)) {
2656 ptr_array_free_strings(reply);
2657 g_ptr_array_free(reply, TRUE);
2662 static gint imap_cmd_copy(IMAPSession * session,
2664 const gchar * destfolder, gint32 * new_uid)
2667 gint32 olduid, newuid;
2672 g_return_val_if_fail(destfolder != NULL, IMAP_ERROR);
2673 g_return_val_if_fail(session != NULL, IMAP_ERROR);
2674 g_return_val_if_fail(new_uid != NULL, IMAP_ERROR);
2676 QUOTE_IF_REQUIRED(destfolder_, destfolder);
2677 imap_gen_send(session, "UID COPY %d %s", msgnum, destfolder_);
2679 reply = g_ptr_array_new();
2682 ok = imap_cmd_ok(session, reply);
2683 if (ok != IMAP_SUCCESS)
2684 log_warning(_("can't copy %d to %s\n"), msgnum, destfolder_);
2685 else if (imap_has_capability(session, "UIDPLUS") && reply->len > 0)
2686 if ((okmsginfo = g_ptr_array_index(reply, reply->len - 1)) != NULL &&
2687 sscanf(okmsginfo, "%*u OK [COPYUID %*u %u %u]", &olduid, &newuid) == 2 &&
2691 ptr_array_free_strings(reply);
2692 g_ptr_array_free(reply, TRUE);
2696 gint imap_cmd_envelope(IMAPSession *session, IMAPSet set)
2698 static GString *header_fields = NULL;
2700 if (header_fields == NULL) {
2701 const HeaderEntry *headers, *elem;
2703 headers = procheader_get_headernames(FALSE);
2704 header_fields = g_string_new("");
2706 for (elem = headers; elem->name != NULL; ++elem) {
2707 gint namelen = strlen(elem->name);
2709 /* Header fields ending with space are not rfc822 headers */
2710 if (elem->name[namelen - 1] == ' ')
2713 /* strip : at the of header field */
2714 if(elem->name[namelen - 1] == ':')
2720 g_string_sprintfa(header_fields, "%s%.*s",
2721 header_fields->str[0] != '\0' ? " " : "",
2722 namelen, elem->name);
2727 (session, "UID FETCH %s (UID FLAGS RFC822.SIZE BODY.PEEK[HEADER.FIELDS (%s)])",
2728 set, header_fields->str);
2730 return IMAP_SUCCESS;
2733 static gint imap_cmd_store(IMAPSession *session, IMAPSet set,
2738 imap_gen_send(session, "UID STORE %s %s",
2741 if ((ok = imap_cmd_ok(session, NULL)) != IMAP_SUCCESS) {
2742 log_warning(_("error while imap command: STORE %s %s\n"),
2747 return IMAP_SUCCESS;
2750 static gint imap_cmd_expunge(IMAPSession *session)
2754 imap_gen_send(session, "EXPUNGE");
2755 if ((ok = imap_cmd_ok(session, NULL)) != IMAP_SUCCESS) {
2756 log_warning(_("error while imap command: EXPUNGE\n"));
2760 return IMAP_SUCCESS;
2763 static gint imap_cmd_ok(IMAPSession *session, GPtrArray *argbuf)
2770 while ((ok = imap_gen_recv(session, &buf))
2772 // make sure data is long enough for any substring of buf
2773 data = alloca(strlen(buf) + 1);
2775 if (buf[0] == '*' && buf[1] == ' ') {
2778 g_ptr_array_add(argbuf, g_strdup(buf + 2));
2780 if (sscanf(buf + 2, "%d %s", &num, data) < 2)
2783 if (!strcmp(data, "EXISTS")) {
2784 session->exists = num;
2785 session->folder_content_changed = TRUE;
2788 if(!strcmp(data, "EXPUNGE")) {
2790 session->folder_content_changed = TRUE;
2796 if (sscanf(buf, "%d %s", &cmd_num, data) < 2) {
2799 } else if (cmd_num == session->cmd_count &&
2800 !strcmp(data, "OK")) {
2802 g_ptr_array_add(argbuf, g_strdup(buf));
2804 return IMAP_SUCCESS;
2815 static void imap_gen_send(IMAPSession *session, const gchar *format, ...)
2822 va_start(args, format);
2823 tmp = g_strdup_vprintf(format, args);
2826 session->cmd_count++;
2828 buf = g_strdup_printf("%d %s\r\n", session->cmd_count, tmp);
2829 if (!strncasecmp(tmp, "LOGIN ", 6) && (p = strchr(tmp + 6, ' '))) {
2831 log_print("IMAP4> %d %s ********\n", session->cmd_count, tmp);
2833 log_print("IMAP4> %d %s\n", session->cmd_count, tmp);
2835 sock_write_all(SESSION(session)->sock, buf, strlen(buf));
2840 static gint imap_gen_recv(IMAPSession *session, gchar **buf)
2842 if ((*buf = sock_getline(SESSION(session)->sock)) == NULL)
2847 log_print("IMAP4< %s\n", *buf);
2849 return IMAP_SUCCESS;
2853 /* misc utility functions */
2855 static gchar *strchr_cpy(const gchar *src, gchar ch, gchar *dest, gint len)
2860 tmp = strchr(src, ch);
2864 memcpy(dest, src, MIN(tmp - src, len - 1));
2865 dest[MIN(tmp - src, len - 1)] = '\0';
2870 static gchar *get_quoted(const gchar *src, gchar ch, gchar *dest, gint len)
2872 const gchar *p = src;
2875 g_return_val_if_fail(*p == ch, NULL);
2880 while (*p != '\0' && *p != ch) {
2882 if (*p == '\\' && *(p + 1) != '\0')
2891 return (gchar *)(*p == ch ? p + 1 : p);
2894 static gchar *search_array_contain_str(GPtrArray *array, const gchar *str)
2898 for (i = 0; i < array->len; i++) {
2901 tmp = g_ptr_array_index(array, i);
2902 if (strstr(tmp, str) != NULL)
2909 static gchar *search_array_str(GPtrArray *array, const gchar *str)
2916 for (i = 0; i < array->len; i++) {
2919 tmp = g_ptr_array_index(array, i);
2920 if (!strncmp(tmp, str, len))
2927 static void imap_path_separator_subst(gchar *str, gchar separator)
2930 gboolean in_escape = FALSE;
2932 if (!separator || separator == '/') return;
2934 for (p = str; *p != '\0'; p++) {
2935 if (*p == '/' && !in_escape)
2937 else if (*p == '&' && *(p + 1) != '-' && !in_escape)
2939 else if (*p == '-' && in_escape)
2944 static gchar *imap_modified_utf7_to_locale(const gchar *mutf7_str)
2947 const gchar *from_p;
2950 to = g_malloc(strlen(mutf7_str) + 1);
2953 for (from_p = mutf7_str; *from_p != '\0'; from_p++) {
2954 if (*from_p == '&' && *(from_p + 1) == '-') {
2964 static iconv_t cd = (iconv_t)-1;
2965 static gboolean iconv_ok = TRUE;
2968 size_t norm_utf7_len;
2970 gchar *to_str, *to_p;
2972 gboolean in_escape = FALSE;
2974 if (!iconv_ok) return g_strdup(mutf7_str);
2976 if (cd == (iconv_t)-1) {
2977 cd = iconv_open(conv_get_current_charset_str(), "UTF-7");
2978 if (cd == (iconv_t)-1) {
2979 g_warning("iconv cannot convert UTF-7 to %s\n",
2980 conv_get_current_charset_str());
2982 return g_strdup(mutf7_str);
2986 norm_utf7 = g_string_new(NULL);
2988 for (p = mutf7_str; *p != '\0'; p++) {
2989 /* replace: '&' -> '+',
2991 escaped ',' -> '/' */
2992 if (!in_escape && *p == '&') {
2993 if (*(p + 1) != '-') {
2994 g_string_append_c(norm_utf7, '+');
2997 g_string_append_c(norm_utf7, '&');
3000 } else if (in_escape && *p == ',') {
3001 g_string_append_c(norm_utf7, '/');
3002 } else if (in_escape && *p == '-') {
3003 g_string_append_c(norm_utf7, '-');
3006 g_string_append_c(norm_utf7, *p);
3010 norm_utf7_p = norm_utf7->str;
3011 norm_utf7_len = norm_utf7->len;
3012 to_len = strlen(mutf7_str) * 5;
3013 to_p = to_str = g_malloc(to_len + 1);
3015 if (iconv(cd, (ICONV_CONST gchar **)&norm_utf7_p, &norm_utf7_len,
3016 &to_p, &to_len) == -1) {
3017 g_warning(_("iconv cannot convert UTF-7 to %s\n"),
3018 conv_get_current_charset_str());
3019 g_string_free(norm_utf7, TRUE);
3021 return g_strdup(mutf7_str);
3024 /* second iconv() call for flushing */
3025 iconv(cd, NULL, NULL, &to_p, &to_len);
3026 g_string_free(norm_utf7, TRUE);
3030 #endif /* !HAVE_ICONV */
3033 static gchar *imap_locale_to_modified_utf7(const gchar *from)
3036 const gchar *from_p;
3039 to = g_malloc(strlen(from) * 2 + 1);
3042 for (from_p = from; *from_p != '\0'; from_p++) {
3043 if (*from_p == '&') {
3053 static iconv_t cd = (iconv_t)-1;
3054 static gboolean iconv_ok = TRUE;
3055 gchar *norm_utf7, *norm_utf7_p;
3056 size_t from_len, norm_utf7_len;
3058 gchar *from_tmp, *to, *p;
3059 gboolean in_escape = FALSE;
3061 if (!iconv_ok) return g_strdup(from);
3063 if (cd == (iconv_t)-1) {
3064 cd = iconv_open("UTF-7", conv_get_current_charset_str());
3065 if (cd == (iconv_t)-1) {
3066 g_warning("iconv cannot convert %s to UTF-7\n",
3067 conv_get_current_charset_str());
3069 return g_strdup(from);
3073 Xstrdup_a(from_tmp, from, return g_strdup(from));
3074 from_len = strlen(from);
3075 norm_utf7_len = from_len * 5;
3076 Xalloca(norm_utf7, norm_utf7_len + 1, return g_strdup(from));
3077 norm_utf7_p = norm_utf7;
3079 #define IS_PRINT(ch) (isprint(ch) && isascii(ch))
3081 while (from_len > 0) {
3082 if (*from_tmp == '+') {
3083 *norm_utf7_p++ = '+';
3084 *norm_utf7_p++ = '-';
3088 } else if (IS_PRINT(*from_tmp)) {
3089 /* printable ascii char */
3090 *norm_utf7_p = *from_tmp;
3096 size_t mb_len = 0, conv_len = 0;
3098 /* unprintable char: convert to UTF-7 */
3100 while (!IS_PRINT(*p) && conv_len < from_len) {
3101 mb_len = mblen(p, MB_LEN_MAX);
3103 g_warning("wrong multibyte sequence\n");
3104 return g_strdup(from);
3110 from_len -= conv_len;
3111 if (iconv(cd, (ICONV_CONST gchar **)&from_tmp,
3113 &norm_utf7_p, &norm_utf7_len) == -1) {
3114 g_warning("iconv cannot convert %s to UTF-7\n",
3115 conv_get_current_charset_str());
3116 return g_strdup(from);
3119 /* second iconv() call for flushing */
3120 iconv(cd, NULL, NULL, &norm_utf7_p, &norm_utf7_len);
3126 *norm_utf7_p = '\0';
3127 to_str = g_string_new(NULL);
3128 for (p = norm_utf7; p < norm_utf7_p; p++) {
3129 /* replace: '&' -> "&-",
3132 BASE64 '/' -> ',' */
3133 if (!in_escape && *p == '&') {
3134 g_string_append(to_str, "&-");
3135 } else if (!in_escape && *p == '+') {
3136 if (*(p + 1) == '-') {
3137 g_string_append_c(to_str, '+');
3140 g_string_append_c(to_str, '&');
3143 } else if (in_escape && *p == '/') {
3144 g_string_append_c(to_str, ',');
3145 } else if (in_escape && *p == '-') {
3146 g_string_append_c(to_str, '-');
3149 g_string_append_c(to_str, *p);
3155 g_string_append_c(to_str, '-');
3159 g_string_free(to_str, FALSE);
3162 #endif /* !HAVE_ICONV */
3165 static gboolean imap_rename_folder_func(GNode *node, gpointer data)
3167 FolderItem *item = node->data;
3168 gchar **paths = data;
3169 const gchar *oldpath = paths[0];
3170 const gchar *newpath = paths[1];
3172 gchar *new_itempath;
3175 oldpathlen = strlen(oldpath);
3176 if (strncmp(oldpath, item->path, oldpathlen) != 0) {
3177 g_warning("path doesn't match: %s, %s\n", oldpath, item->path);
3181 base = item->path + oldpathlen;
3182 while (*base == G_DIR_SEPARATOR) base++;
3184 new_itempath = g_strdup(newpath);
3186 new_itempath = g_strconcat(newpath, G_DIR_SEPARATOR_S, base,
3189 item->path = new_itempath;
3194 static gint get_list_of_uids(Folder *folder, IMAPFolderItem *item, GSList **msgnum_list)
3196 gint ok, nummsgs = 0, lastuid_old;
3197 IMAPSession *session;
3198 GSList *uidlist, *elem;
3201 session = imap_session_get(folder);
3202 g_return_val_if_fail(session != NULL, -1);
3204 ok = imap_select(session, IMAP_FOLDER(folder), item->item.path,
3205 NULL, NULL, NULL, NULL);
3206 if (ok != IMAP_SUCCESS)
3209 cmd_buf = g_strdup_printf("UID %d:*", item->lastuid + 1);
3210 ok = imap_cmd_search(session, cmd_buf, &uidlist);
3213 if (ok == IMAP_SOCKET) {
3214 session_destroy((Session *)session);
3215 ((RemoteFolder *)folder)->session = NULL;
3219 if (ok != IMAP_SUCCESS) {
3223 argbuf = g_ptr_array_new();
3225 cmd_buf = g_strdup_printf("UID FETCH %d:* (UID)", item->lastuid + 1);
3226 imap_gen_send(session, cmd_buf);
3228 ok = imap_cmd_ok(session, argbuf);
3229 if (ok != IMAP_SUCCESS) {
3230 ptr_array_free_strings(argbuf);
3231 g_ptr_array_free(argbuf, TRUE);
3235 for(i = 0; i < argbuf->len; i++) {
3238 if((ret = sscanf(g_ptr_array_index(argbuf, i),
3239 "%*d FETCH (UID %d)", &msgnum)) == 1)
3240 uidlist = g_slist_prepend(uidlist, GINT_TO_POINTER(msgnum));
3242 ptr_array_free_strings(argbuf);
3243 g_ptr_array_free(argbuf, TRUE);
3246 lastuid_old = item->lastuid;
3247 *msgnum_list = g_slist_copy(item->uid_list);
3248 nummsgs = g_slist_length(*msgnum_list);
3249 debug_print("Got %d uids from cache\n", g_slist_length(item->uid_list));
3251 for (elem = uidlist; elem != NULL; elem = g_slist_next(elem)) {
3254 msgnum = GPOINTER_TO_INT(elem->data);
3255 if (msgnum > lastuid_old) {
3256 *msgnum_list = g_slist_prepend(*msgnum_list, GINT_TO_POINTER(msgnum));
3257 item->uid_list = g_slist_prepend(item->uid_list, GINT_TO_POINTER(msgnum));
3260 if(msgnum > item->lastuid)
3261 item->lastuid = msgnum;
3264 g_slist_free(uidlist);
3269 gint imap_get_num_list(Folder *folder, FolderItem *_item, GSList **msgnum_list)
3271 IMAPFolderItem *item = (IMAPFolderItem *)_item;
3272 IMAPSession *session;
3273 gint ok, nummsgs = 0, exists, recent, unseen, uid_val, uid_next;
3276 gboolean selected_folder;
3278 g_return_val_if_fail(folder != NULL, -1);
3279 g_return_val_if_fail(item != NULL, -1);
3280 g_return_val_if_fail(item->item.path != NULL, -1);
3281 g_return_val_if_fail(FOLDER_CLASS(folder) == &imap_class, -1);
3282 g_return_val_if_fail(folder->account != NULL, -1);
3284 session = imap_session_get(folder);
3285 g_return_val_if_fail(session != NULL, -1);
3287 selected_folder = (session->mbox != NULL) &&
3288 (!strcmp(session->mbox, item->item.path));
3289 if (selected_folder) {
3290 ok = imap_cmd_noop(session);
3291 if (ok != IMAP_SUCCESS)
3293 exists = session->exists;
3295 ok = imap_status(session, IMAP_FOLDER(folder), item->item.path,
3296 &exists, &recent, &uid_next, &uid_val, &unseen);
3297 if (ok != IMAP_SUCCESS)
3301 /* If old uid_next matches new uid_next we can be sure no message
3302 was added to the folder */
3303 if (( selected_folder && !session->folder_content_changed) ||
3304 (!selected_folder && uid_next == item->uid_next)) {
3305 nummsgs = g_slist_length(item->uid_list);
3307 /* If number of messages is still the same we
3308 know our caches message numbers are still valid,
3309 otherwise if the number of messages has decrease
3310 we discard our cache to start a new scan to find
3311 out which numbers have been removed */
3312 if (exists == nummsgs) {
3313 *msgnum_list = g_slist_copy(item->uid_list);
3315 } else if (exists < nummsgs) {
3316 debug_print("Freeing imap uid cache");
3318 g_slist_free(item->uid_list);
3319 item->uid_list = NULL;
3322 if (!selected_folder)
3323 item->uid_next = uid_next;
3326 *msgnum_list = NULL;
3330 nummsgs = get_list_of_uids(folder, item, &uidlist);
3332 if (nummsgs != exists) {
3333 /* Cache contains more messages then folder, we have cached
3334 an old UID of a message that was removed and new messages
3335 have been added too, otherwise the uid_next check would
3337 debug_print("Freeing imap uid cache");
3339 g_slist_free(item->uid_list);
3340 item->uid_list = NULL;
3342 g_slist_free(*msgnum_list);
3344 nummsgs = get_list_of_uids(folder, item, &uidlist);
3347 *msgnum_list = uidlist;
3349 dir = folder_item_get_path((FolderItem *)item);
3350 debug_print("removing old messages from %s\n", dir);
3351 remove_numbered_files_not_in_list(dir, *msgnum_list);
3357 static MsgInfo *imap_parse_msg(const gchar *file, FolderItem *item)
3362 flags.perm_flags = MSG_NEW|MSG_UNREAD;
3363 flags.tmp_flags = 0;
3365 g_return_val_if_fail(item != NULL, NULL);
3366 g_return_val_if_fail(file != NULL, NULL);
3368 if (item->stype == F_QUEUE) {
3369 MSG_SET_TMP_FLAGS(flags, MSG_QUEUED);
3370 } else if (item->stype == F_DRAFT) {
3371 MSG_SET_TMP_FLAGS(flags, MSG_DRAFT);
3374 msginfo = procheader_parse_file(file, flags, FALSE, FALSE);
3375 if (!msginfo) return NULL;
3377 msginfo->folder = item;
3382 GSList *imap_get_msginfos(Folder *folder, FolderItem *item, GSList *msgnum_list)
3384 IMAPSession *session;
3385 MsgInfoList *ret = NULL;
3388 g_return_val_if_fail(folder != NULL, NULL);
3389 g_return_val_if_fail(item != NULL, NULL);
3390 g_return_val_if_fail(msgnum_list != NULL, NULL);
3392 session = imap_session_get(folder);
3393 g_return_val_if_fail(session != NULL, NULL);
3395 ok = imap_select(session, IMAP_FOLDER(folder), item->path,
3396 NULL, NULL, NULL, NULL);
3397 if (ok != IMAP_SUCCESS)
3400 if (!(item->stype == F_QUEUE || item->stype == F_DRAFT)) {
3401 ret = g_slist_concat(ret,
3402 imap_get_uncached_messages(
3403 session, item, msgnum_list));
3405 MsgNumberList *sorted_list, *elem;
3406 gint startnum, lastnum;
3408 sorted_list = g_slist_sort(g_slist_copy(msgnum_list), g_int_compare);
3410 startnum = lastnum = GPOINTER_TO_INT(sorted_list->data);
3412 for (elem = sorted_list;; elem = g_slist_next(elem)) {
3416 num = GPOINTER_TO_INT(elem->data);
3418 if (num > lastnum + 1 || elem == NULL) {
3420 for (i = startnum; i <= lastnum; ++i) {
3423 file = imap_fetch_msg(folder, item, i);
3425 MsgInfo *msginfo = imap_parse_msg(file, item);
3426 if (msginfo != NULL) {
3427 msginfo->msgnum = i;
3428 ret = g_slist_append(ret, msginfo);
3442 g_slist_free(sorted_list);
3448 MsgInfo *imap_get_msginfo(Folder *folder, FolderItem *item, gint uid)
3450 MsgInfo *msginfo = NULL;
3451 MsgInfoList *msginfolist;
3452 MsgNumberList numlist;
3454 numlist.next = NULL;
3455 numlist.data = GINT_TO_POINTER(uid);
3457 msginfolist = imap_get_msginfos(folder, item, &numlist);
3458 if (msginfolist != NULL) {
3459 msginfo = msginfolist->data;
3460 g_slist_free(msginfolist);
3466 gboolean imap_check_msgnum_validity(Folder *folder, FolderItem *_item)
3468 IMAPSession *session;
3469 IMAPFolderItem *item = (IMAPFolderItem *)_item;
3470 gint ok, exists = 0, recent = 0, unseen = 0;
3471 guint32 uid_next, uid_validity = 0;
3473 g_return_val_if_fail(folder != NULL, FALSE);
3474 g_return_val_if_fail(item != NULL, FALSE);
3475 g_return_val_if_fail(item->item.folder != NULL, FALSE);
3476 g_return_val_if_fail(FOLDER_CLASS(item->item.folder) == &imap_class, FALSE);
3478 session = imap_session_get(folder);
3479 g_return_val_if_fail(session != NULL, FALSE);
3481 ok = imap_status(session, IMAP_FOLDER(folder), item->item.path,
3482 &exists, &recent, &uid_next, &uid_validity, &unseen);
3483 if (ok != IMAP_SUCCESS)
3486 if(item->item.mtime == uid_validity)
3489 debug_print("Freeing imap uid cache\n");
3491 g_slist_free(item->uid_list);
3492 item->uid_list = NULL;
3494 item->item.mtime = uid_validity;
3496 imap_delete_all_cached_messages((FolderItem *)item);
3501 void imap_change_flags(Folder *folder, FolderItem *item, MsgInfo *msginfo, MsgPermFlags newflags)
3503 IMAPSession *session;
3504 IMAPFlags flags_set = 0, flags_unset = 0;
3505 gint ok = IMAP_SUCCESS;
3506 MsgNumberList numlist;
3508 g_return_if_fail(folder != NULL);
3509 g_return_if_fail(folder->klass == &imap_class);
3510 g_return_if_fail(item != NULL);
3511 g_return_if_fail(item->folder == folder);
3512 g_return_if_fail(msginfo != NULL);
3513 g_return_if_fail(msginfo->folder == item);
3515 session = imap_session_get(folder);
3516 if (!session) return;
3518 if ((ok = imap_select(session, IMAP_FOLDER(folder), msginfo->folder->path,
3519 NULL, NULL, NULL, NULL)) != IMAP_SUCCESS)
3522 if (!MSG_IS_MARKED(msginfo->flags) && (newflags & MSG_MARKED))
3523 flags_set |= IMAP_FLAG_FLAGGED;
3524 if ( MSG_IS_MARKED(msginfo->flags) && !(newflags & MSG_MARKED))
3525 flags_unset |= IMAP_FLAG_FLAGGED;
3527 if (!MSG_IS_UNREAD(msginfo->flags) && (newflags & MSG_UNREAD))
3528 flags_unset |= IMAP_FLAG_SEEN;
3529 if ( MSG_IS_UNREAD(msginfo->flags) && !(newflags & MSG_UNREAD))
3530 flags_set |= IMAP_FLAG_SEEN;
3532 if (!MSG_IS_REPLIED(msginfo->flags) && (newflags & MSG_REPLIED))
3533 flags_set |= IMAP_FLAG_ANSWERED;
3534 if ( MSG_IS_REPLIED(msginfo->flags) && !(newflags & MSG_REPLIED))
3535 flags_set |= IMAP_FLAG_ANSWERED;
3537 numlist.next = NULL;
3538 numlist.data = GINT_TO_POINTER(msginfo->msgnum);
3541 ok = imap_set_message_flags(session, &numlist, flags_set, TRUE);
3542 if (ok != IMAP_SUCCESS) return;
3546 ok = imap_set_message_flags(session, &numlist, flags_unset, FALSE);
3547 if (ok != IMAP_SUCCESS) return;
3550 msginfo->flags.perm_flags = newflags;