cd22ddd0255e8220b9d3085fd17244302f40cb68
[claws.git] / src / imap.c
1 /*
2  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 1999-2003 Hiroyuki Yamamoto
4  *
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.
9  *
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.
14  *
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.
18  */
19
20 #ifdef HAVE_CONFIG_H
21 #  include "config.h"
22 #endif
23
24 #include "defs.h"
25
26 #include <glib.h>
27 #include <stdio.h>
28 #include <string.h>
29 #include <stdlib.h>
30 #include <dirent.h>
31 #include <unistd.h>
32 #include <ctype.h>
33 #include <time.h>
34 #if HAVE_ICONV
35 #  include <iconv.h>
36 #endif
37
38 #if USE_OPENSSL
39 #  include "ssl.h"
40 #endif
41 #include "folder.h"
42 #include "session.h"
43 #include "procmsg.h"
44 #include "intl.h"
45 #include "imap.h"
46 #include "socket.h"
47 #include "ssl.h"
48 #include "recv.h"
49 #include "procheader.h"
50 #include "prefs_account.h"
51 #include "codeconv.h"
52 #include "md5.h"
53 #include "base64.h"
54 #include "utils.h"
55 #include "inputdialog.h"
56 #include "log.h"
57
58 typedef struct _IMAPFolder      IMAPFolder;
59 typedef struct _IMAPSession     IMAPSession;
60 typedef struct _IMAPNameSpace   IMAPNameSpace;
61 typedef struct _IMAPFolderItem  IMAPFolderItem;
62
63 #include "prefs_account.h"
64
65 #define IMAP_FOLDER(obj)        ((IMAPFolder *)obj)
66 #define IMAP_FOLDER_ITEM(obj)   ((IMAPFolderItem *)obj)
67 #define IMAP_SESSION(obj)       ((IMAPSession *)obj)
68
69 struct _IMAPFolder
70 {
71         RemoteFolder rfolder;
72
73         /* list of IMAPNameSpace */
74         GList *ns_personal;
75         GList *ns_others;
76         GList *ns_shared;
77 };
78
79 struct _IMAPSession
80 {
81         Session session;
82
83         gboolean authenticated;
84
85         gchar **capability;
86         gboolean uidplus;
87
88         gchar *mbox;
89         guint cmd_count;
90
91         /* CLAWS */
92         time_t last_access_time;
93         gboolean folder_content_changed;
94         guint exists;
95 };
96
97 struct _IMAPNameSpace
98 {
99         gchar *name;
100         gchar separator;
101 };
102
103 #define IMAP_SUCCESS    0
104 #define IMAP_SOCKET     2
105 #define IMAP_AUTHFAIL   3
106 #define IMAP_PROTOCOL   4
107 #define IMAP_SYNTAX     5
108 #define IMAP_IOERR      6
109 #define IMAP_ERROR      7
110
111 #define IMAPBUFSIZE     8192
112
113 typedef enum
114 {
115         IMAP_FLAG_SEEN          = 1 << 0,
116         IMAP_FLAG_ANSWERED      = 1 << 1,
117         IMAP_FLAG_FLAGGED       = 1 << 2,
118         IMAP_FLAG_DELETED       = 1 << 3,
119         IMAP_FLAG_DRAFT         = 1 << 4
120 } IMAPFlags;
121
122 #define IMAP_IS_SEEN(flags)     ((flags & IMAP_FLAG_SEEN) != 0)
123 #define IMAP_IS_ANSWERED(flags) ((flags & IMAP_FLAG_ANSWERED) != 0)
124 #define IMAP_IS_FLAGGED(flags)  ((flags & IMAP_FLAG_FLAGGED) != 0)
125 #define IMAP_IS_DELETED(flags)  ((flags & IMAP_FLAG_DELETED) != 0)
126 #define IMAP_IS_DRAFT(flags)    ((flags & IMAP_FLAG_DRAFT) != 0)
127
128
129 #define IMAP4_PORT      143
130 #if USE_OPENSSL
131 #define IMAPS_PORT      993
132 #endif
133
134 #define IMAP_CMD_LIMIT  1000
135
136 #define QUOTE_IF_REQUIRED(out, str)                             \
137 {                                                               \
138         if (*str != '"' && strpbrk(str, " \t(){}%*") != NULL) { \
139                 gchar *__tmp;                                   \
140                 gint len;                                       \
141                                                                 \
142                 len = strlen(str) + 3;                          \
143                 Xalloca(__tmp, len, return IMAP_ERROR);         \
144                 g_snprintf(__tmp, len, "\"%s\"", str);          \
145                 out = __tmp;                                    \
146         } else {                                                \
147                 Xstrdup_a(out, str, return IMAP_ERROR);         \
148         }                                                       \
149 }
150
151 typedef gchar * IMAPSet;
152
153 struct _IMAPFolderItem
154 {
155         FolderItem item;
156
157         guint lastuid;
158         guint uid_next;
159         GSList *uid_list;
160 };
161
162 static Folder *imap_folder_new(const gchar * name, const gchar * path);
163 static void imap_folder_destroy(Folder * folder);
164
165 static IMAPSession *imap_session_new(const PrefsAccount * account);
166 static void imap_session_authenticate(IMAPSession * session,
167                                       const PrefsAccount * account);
168 static void imap_session_destroy(Session * session);
169
170 static gchar *imap_fetch_msg(Folder * folder, FolderItem * item, gint uid);
171 static gint imap_add_msg(Folder * folder,
172                          FolderItem * dest,
173                          const gchar * file, MsgFlags * flags);
174 static gint imap_add_msgs(Folder * folder, FolderItem * dest,
175                           GSList * file_list,
176                           GRelation *relation);
177
178 static gint imap_copy_msg(Folder * folder,
179                           FolderItem * dest, MsgInfo * msginfo);
180 static gint imap_copy_msgs(Folder *folder, FolderItem *dest, 
181                     MsgInfoList *msglist, GRelation *relation);
182
183 static gint imap_remove_msg(Folder * folder, FolderItem * item, gint uid);
184 static gint imap_remove_all_msg(Folder * folder, FolderItem * item);
185
186 static gboolean imap_is_msg_changed(Folder * folder,
187                                     FolderItem * item, MsgInfo * msginfo);
188
189 static gint imap_close(Folder * folder, FolderItem * item);
190
191 static gint imap_scan_tree(Folder * folder);
192
193 static gint imap_create_tree(Folder * folder);
194
195 static FolderItem *imap_create_folder(Folder * folder,
196                                       FolderItem * parent,
197                                       const gchar * name);
198 static gint imap_rename_folder(Folder * folder,
199                                FolderItem * item, const gchar * name);
200 static gint imap_remove_folder(Folder * folder, FolderItem * item);
201
202
203 static void imap_folder_init            (Folder         *folder,
204                                          const gchar    *name,
205                                          const gchar    *path);
206
207 static FolderItem *imap_folder_item_new (Folder         *folder);
208 static void imap_folder_item_destroy    (Folder         *folder,
209                                          FolderItem     *item);
210
211 static IMAPSession *imap_session_get    (Folder         *folder);
212
213 static gint imap_greeting               (IMAPSession    *session);
214 static gint imap_auth                   (IMAPSession    *session,
215                                          const gchar    *user,
216                                          const gchar    *pass,
217                                          IMAPAuthType    type);
218
219 static gint imap_scan_tree_recursive    (IMAPSession    *session,
220                                          FolderItem     *item);
221 static GSList *imap_parse_list          (IMAPFolder     *folder,
222                                          IMAPSession    *session,
223                                          const gchar    *real_path,
224                                          gchar          *separator);
225
226 static void imap_create_missing_folders (Folder                 *folder);
227 static FolderItem *imap_create_special_folder
228                                         (Folder                 *folder,
229                                          SpecialFolderItemType   stype,
230                                          const gchar            *name);
231
232 static gint imap_do_copy_msgs           (Folder         *folder,
233                                          FolderItem     *dest,
234                                          MsgInfoList    *msglist,
235                                          GRelation      *relation);
236
237 static void imap_delete_all_cached_messages     (FolderItem     *item);
238
239 #if USE_OPENSSL
240 static SockInfo *imap_open              (const gchar    *server,
241                                          gushort         port,
242                                          SSLType         ssl_type);
243 #else
244 static SockInfo *imap_open              (const gchar    *server,
245                                          gushort         port);
246 #endif
247
248 #if USE_OPENSSL
249 static SockInfo *imap_open_tunnel(const gchar *server,
250                                   const gchar *tunnelcmd,
251                                   SSLType ssl_type);
252 #else
253 static SockInfo *imap_open_tunnel(const gchar *server,
254                                   const gchar *tunnelcmd);
255 #endif
256
257 #if USE_OPENSSL
258 static SockInfo *imap_init_sock(SockInfo *sock, SSLType ssl_type);
259 #else
260 static SockInfo *imap_init_sock(SockInfo *sock);
261 #endif
262
263 static gchar *imap_get_flag_str         (IMAPFlags       flags);
264 static gint imap_set_message_flags      (IMAPSession    *session,
265                                          MsgNumberList  *numlist,
266                                          IMAPFlags       flags,
267                                          gboolean        is_set);
268 static gint imap_select                 (IMAPSession    *session,
269                                          IMAPFolder     *folder,
270                                          const gchar    *path,
271                                          gint           *exists,
272                                          gint           *recent,
273                                          gint           *unseen,
274                                          guint32        *uid_validity);
275 static gint imap_status                 (IMAPSession    *session,
276                                          IMAPFolder     *folder,
277                                          const gchar    *path,
278                                          gint           *messages,
279                                          gint           *recent,
280                                          guint32        *uid_next,
281                                          guint32        *uid_validity,
282                                          gint           *unseen);
283
284 static void imap_parse_namespace                (IMAPSession    *session,
285                                                  IMAPFolder     *folder);
286 static void imap_get_namespace_by_list          (IMAPSession    *session,
287                                                  IMAPFolder     *folder);
288 static IMAPNameSpace *imap_find_namespace       (IMAPFolder     *folder,
289                                                  const gchar    *path);
290 static gchar imap_get_path_separator            (IMAPFolder     *folder,
291                                                  const gchar    *path);
292 static gchar *imap_get_real_path                (IMAPFolder     *folder,
293                                                  const gchar    *path);
294
295 static gchar *imap_parse_atom           (SockInfo       *sock,
296                                          gchar          *src,
297                                          gchar          *dest,
298                                          gint            dest_len,
299                                          GString        *str);
300 static MsgFlags imap_parse_flags        (const gchar    *flag_str);
301 static MsgInfo *imap_parse_envelope     (SockInfo       *sock,
302                                          FolderItem     *item,
303                                          GString        *line_str);
304
305 static gboolean imap_has_capability     (IMAPSession    *session,
306                                          const gchar    *cap);
307 static void imap_free_capabilities      (IMAPSession    *session);
308
309 /* low-level IMAP4rev1 commands */
310 static gint imap_cmd_authenticate
311                                 (IMAPSession    *session,
312                                  const gchar    *user,
313                                  const gchar    *pass,
314                                  IMAPAuthType    type);
315 static gint imap_cmd_login      (IMAPSession    *session,
316                                  const gchar    *user,
317                                  const gchar    *pass);
318 static gint imap_cmd_logout     (IMAPSession    *session);
319 static gint imap_cmd_noop       (IMAPSession    *session);
320 static gint imap_cmd_starttls   (IMAPSession    *session);
321 static gint imap_cmd_namespace  (IMAPSession    *session,
322                                  gchar         **ns_str);
323 static gint imap_cmd_list       (IMAPSession    *session,
324                                  const gchar    *ref,
325                                  const gchar    *mailbox,
326                                  GPtrArray      *argbuf);
327 static gint imap_cmd_do_select  (IMAPSession    *session,
328                                  const gchar    *folder,
329                                  gboolean        examine,
330                                  gint           *exists,
331                                  gint           *recent,
332                                  gint           *unseen,
333                                  guint32        *uid_validity);
334 static gint imap_cmd_select     (IMAPSession    *session,
335                                  const gchar    *folder,
336                                  gint           *exists,
337                                  gint           *recent,
338                                  gint           *unseen,
339                                  guint32        *uid_validity);
340 static gint imap_cmd_examine    (IMAPSession    *session,
341                                  const gchar    *folder,
342                                  gint           *exists,
343                                  gint           *recent,
344                                  gint           *unseen,
345                                  guint32        *uid_validity);
346 static gint imap_cmd_create     (IMAPSession    *sock,
347                                  const gchar    *folder);
348 static gint imap_cmd_rename     (IMAPSession    *sock,
349                                  const gchar    *oldfolder,
350                                  const gchar    *newfolder);
351 static gint imap_cmd_delete     (IMAPSession    *session,
352                                  const gchar    *folder);
353 static gint imap_cmd_envelope   (IMAPSession    *session,
354                                  IMAPSet         set);
355 static gint imap_cmd_fetch      (IMAPSession    *sock,
356                                  guint32         uid,
357                                  const gchar    *filename);
358 static gint imap_cmd_append     (IMAPSession    *session,
359                                  const gchar    *destfolder,
360                                  const gchar    *file,
361                                  IMAPFlags       flags,
362                                  guint32        *new_uid);
363 static gint imap_cmd_copy       (IMAPSession    *session, 
364                                  const gchar    *seq_set, 
365                                  const gchar    *destfolder,
366                                  GRelation      *uid_mapping);
367 static gint imap_cmd_store      (IMAPSession    *session,
368                                  IMAPSet         set,
369                                  gchar          *sub_cmd);
370 static gint imap_cmd_expunge    (IMAPSession    *session,
371                                  IMAPSet         seq_set);
372 static gint imap_cmd_close      (IMAPSession    *session);
373
374 static gint imap_cmd_ok         (IMAPSession    *session,
375                                  GPtrArray      *argbuf);
376 static void imap_gen_send       (IMAPSession    *session,
377                                  const gchar    *format, ...);
378 static gint imap_gen_recv       (IMAPSession    *session,
379                                  gchar         **ret);
380
381 /* misc utility functions */
382 static gchar *strchr_cpy                        (const gchar    *src,
383                                                  gchar           ch,
384                                                  gchar          *dest,
385                                                  gint            len);
386 static gchar *get_quoted                        (const gchar    *src,
387                                                  gchar           ch,
388                                                  gchar          *dest,
389                                                  gint            len);
390 static gchar *search_array_contain_str          (GPtrArray      *array,
391                                                  const gchar    *str);
392 static gchar *search_array_str                  (GPtrArray      *array,
393                                                  const gchar    *str);
394 static void imap_path_separator_subst           (gchar          *str,
395                                                  gchar           separator);
396
397 static gchar *imap_modified_utf7_to_locale      (const gchar    *mutf7_str);
398 static gchar *imap_locale_to_modified_utf7      (const gchar    *from);
399
400 static GSList *imap_get_seq_set_from_numlist    (MsgNumberList  *msglist);
401 static GSList *imap_get_seq_set_from_msglist    (MsgInfoList    *msglist);
402 static void imap_seq_set_free                   (GSList         *seq_list);
403
404 static gboolean imap_rename_folder_func         (GNode          *node,
405                                                  gpointer        data);
406 static gint imap_get_num_list                   (Folder         *folder,
407                                                  FolderItem     *item,
408                                                  GSList        **list,
409                                                  gboolean       *old_uids_valid);
410 static GSList *imap_get_msginfos                (Folder         *folder,
411                                                  FolderItem     *item,
412                                                  GSList         *msgnum_list);
413 static MsgInfo *imap_get_msginfo                (Folder         *folder,
414                                                  FolderItem     *item,
415                                                  gint            num);
416 static gboolean imap_scan_required              (Folder         *folder,
417                                                  FolderItem     *item);
418 static void imap_change_flags                   (Folder         *folder,
419                                                  FolderItem     *item,
420                                                  MsgInfo        *msginfo,
421                                                  MsgPermFlags   newflags);
422 static gchar *imap_folder_get_path              (Folder         *folder);
423 static gchar *imap_item_get_path                (Folder         *folder,
424                                                  FolderItem     *item);
425
426 FolderClass imap_class =
427 {
428         F_IMAP,
429         "imap",
430         "IMAP4",
431
432         /* Folder functions */
433         imap_folder_new,
434         imap_folder_destroy,
435         imap_scan_tree,
436         imap_create_tree,
437
438         /* FolderItem functions */
439         imap_folder_item_new,
440         imap_folder_item_destroy,
441         imap_item_get_path,
442         imap_create_folder,
443         imap_rename_folder,
444         imap_remove_folder,
445         imap_close,
446         imap_get_num_list,
447         NULL,
448         NULL,
449         NULL,
450         imap_scan_required,
451
452         /* Message functions */
453         imap_get_msginfo,
454         imap_get_msginfos,
455         imap_fetch_msg,
456         imap_add_msg,
457         imap_add_msgs,
458         imap_copy_msg,
459         imap_copy_msgs,
460         imap_remove_msg,
461         imap_remove_all_msg,
462         imap_is_msg_changed,
463         imap_change_flags,
464 };
465
466 FolderClass *imap_get_class(void)
467 {
468         return &imap_class;
469 }
470
471 Folder *imap_folder_new(const gchar *name, const gchar *path)
472 {
473         Folder *folder;
474
475         folder = (Folder *)g_new0(IMAPFolder, 1);
476         folder->klass = &imap_class;
477         imap_folder_init(folder, name, path);
478
479         return folder;
480 }
481
482 void imap_folder_destroy(Folder *folder)
483 {
484         gchar *dir;
485
486         dir = imap_folder_get_path(folder);
487         if (is_dir_exist(dir))
488                 remove_dir_recursive(dir);
489         g_free(dir);
490
491         folder_remote_folder_destroy(REMOTE_FOLDER(folder));
492 }
493
494 static void imap_folder_init(Folder *folder, const gchar *name,
495                              const gchar *path)
496 {
497         folder_remote_folder_init((Folder *)folder, name, path);
498 }
499
500 static FolderItem *imap_folder_item_new(Folder *folder)
501 {
502         IMAPFolderItem *item;
503         
504         item = g_new0(IMAPFolderItem, 1);
505         item->lastuid = 0;
506         item->uid_next = 0;
507         item->uid_list = NULL;
508
509         return (FolderItem *)item;
510 }
511
512 static void imap_folder_item_destroy(Folder *folder, FolderItem *_item)
513 {
514         IMAPFolderItem *item = (IMAPFolderItem *)_item;
515
516         g_return_if_fail(item != NULL);
517         g_slist_free(item->uid_list);
518
519         g_free(_item);
520 }
521
522 static gboolean imap_reset_uid_lists_func(GNode *node, gpointer data)
523 {
524         IMAPFolderItem *item = (IMAPFolderItem *)node->data;
525         
526         item->lastuid = 0;
527         item->uid_next = 0;
528         g_slist_free(item->uid_list);
529         item->uid_list = NULL;
530         
531         return FALSE;
532 }
533
534 static void imap_reset_uid_lists(Folder *folder)
535 {
536         if(folder->node == NULL)
537                 return;
538         
539         /* Destroy all uid lists and rest last uid */
540         g_node_traverse(folder->node, G_IN_ORDER, G_TRAVERSE_ALL, -1, imap_reset_uid_lists_func, NULL); 
541 }
542
543 /* Send CAPABILITY, and examine the server's response to see whether this
544  * connection is pre-authenticated or not and build a list of CAPABILITIES. */
545 static gint imap_greeting(IMAPSession *session)
546 {
547         gchar *capstr;
548         GPtrArray *argbuf;
549
550         imap_gen_send(session, "CAPABILITY");
551         
552         argbuf = g_ptr_array_new();
553
554         if (imap_cmd_ok(session, argbuf) != IMAP_SUCCESS ||
555             ((capstr = search_array_str(argbuf, "CAPABILITY ")) == NULL)) {
556                 ptr_array_free_strings(argbuf);
557                 g_ptr_array_free(argbuf, TRUE);
558                 return -1;
559         }
560
561         session->authenticated = search_array_str(argbuf, "PREAUTH") != NULL;
562         
563         capstr += strlen("CAPABILITY ");
564
565         IMAP_SESSION(session)->capability = g_strsplit(capstr, " ", 0);
566         
567         ptr_array_free_strings(argbuf);
568         g_ptr_array_free(argbuf, TRUE);
569
570         if (imap_has_capability(session, "UIDPLUS")) 
571                 session->uidplus = TRUE; 
572
573         return 0;
574 }
575
576 static gint imap_auth(IMAPSession *session, const gchar *user, const gchar *pass,
577                       IMAPAuthType type)
578 {
579         gint ok;
580
581         if (type == 0 || type == IMAP_AUTH_LOGIN)
582                 ok = imap_cmd_login(session, user, pass);
583         else
584                 ok = imap_cmd_authenticate(session, user, pass, type);
585
586         if (ok == IMAP_SUCCESS)
587                 session->authenticated = TRUE;
588
589         return ok;
590 }
591
592 static IMAPSession *imap_session_get(Folder *folder)
593 {
594         RemoteFolder *rfolder = REMOTE_FOLDER(folder);
595         IMAPSession *session = NULL;
596
597         g_return_val_if_fail(folder != NULL, NULL);
598         g_return_val_if_fail(FOLDER_CLASS(folder) == &imap_class, NULL);
599         g_return_val_if_fail(folder->account != NULL, NULL);
600
601         /* Make sure we have a session */
602         if (rfolder->session != NULL) {
603                 session = IMAP_SESSION(rfolder->session);
604         } else {
605                 imap_reset_uid_lists(folder);
606                 session = imap_session_new(folder->account);
607         }
608         if(session == NULL)
609                 return NULL;
610
611         if (SESSION(session)->sock->state == CONN_DISCONNECTED) {
612                 debug_print("IMAP server disconnected\n");
613                 session_destroy(SESSION(session));
614                 imap_reset_uid_lists(folder);
615                 session = imap_session_new(folder->account);
616         }
617
618         /* Make sure session is authenticated */
619         if (!IMAP_SESSION(session)->authenticated)
620                 imap_session_authenticate(IMAP_SESSION(session), folder->account);
621         if (!IMAP_SESSION(session)->authenticated) {
622                 session_destroy(SESSION(session));
623                 rfolder->session = NULL;
624                 return NULL;
625         }
626
627         /* Make sure we have parsed the IMAP namespace */
628         imap_parse_namespace(IMAP_SESSION(session),
629                              IMAP_FOLDER(folder));
630
631         /* I think the point of this code is to avoid sending a
632          * keepalive if we've used the session recently and therefore
633          * think it's still alive.  Unfortunately, most of the code
634          * does not yet check for errors on the socket, and so if the
635          * connection drops we don't notice until the timeout expires.
636          * A better solution than sending a NOOP every time would be
637          * for every command to be prepared to retry until it is
638          * successfully sent. -- mbp */
639         if (time(NULL) - session->last_access_time > SESSION_TIMEOUT) {
640                 /* verify that the session is still alive */
641                 if (imap_cmd_noop(session) != IMAP_SUCCESS) {
642                         /* Check if this is the first try to establish a
643                            connection, if yes we don't try to reconnect */
644                         if (rfolder->session == NULL) {
645                                 log_warning(_("Connecting %s failed"),
646                                             folder->account->recv_server);
647                                 session_destroy(SESSION(session));
648                                 session = NULL;
649                         } else {
650                                 log_warning(_("IMAP4 connection to %s has been"
651                                               " disconnected. Reconnecting...\n"),
652                                             folder->account->recv_server);
653                                 session_destroy(SESSION(session));
654                                 /* Clear folders session to make imap_session_get create
655                                    a new session, because of rfolder->session == NULL
656                                    it will not try to reconnect again and so avoid an
657                                    endless loop */
658                                 rfolder->session = NULL;
659                                 session = imap_session_get(folder);
660                         }
661                 }
662         }
663
664         rfolder->session = SESSION(session);
665         if (session) {
666                 session->last_access_time = time(NULL);
667         }
668         return IMAP_SESSION(session);
669 }
670
671 IMAPSession *imap_session_new(const PrefsAccount *account)
672 {
673         IMAPSession *session;
674         SockInfo *imap_sock;
675         gushort port;
676
677 #ifdef USE_OPENSSL
678         /* FIXME: IMAP over SSL only... */ 
679         SSLType ssl_type;
680
681         port = account->set_imapport ? account->imapport
682                 : account->ssl_imap == SSL_TUNNEL ? IMAPS_PORT : IMAP4_PORT;
683         ssl_type = account->ssl_imap;   
684 #else
685         port = account->set_imapport ? account->imapport
686                 : IMAP4_PORT;
687 #endif
688
689         if (account->set_tunnelcmd) {
690                 log_message(_("creating tunneled IMAP4 connection\n"));
691 #if USE_OPENSSL
692                 if ((imap_sock = imap_open_tunnel(account->recv_server, 
693                                                   account->tunnelcmd,
694                                                   ssl_type)) == NULL)
695 #else
696                 if ((imap_sock = imap_open_tunnel(account->recv_server, 
697                                                   account->tunnelcmd)) == NULL)
698 #endif
699                         return NULL;
700         } else {
701                 g_return_val_if_fail(account->recv_server != NULL, NULL);
702
703                 log_message(_("creating IMAP4 connection to %s:%d ...\n"),
704                             account->recv_server, port);
705                 
706 #if USE_OPENSSL
707                 if ((imap_sock = imap_open(account->recv_server, port,
708                                            ssl_type)) == NULL)
709 #else
710                 if ((imap_sock = imap_open(account->recv_server, port)) == NULL)
711 #endif
712                         return NULL;
713         }
714
715         session = g_new0(IMAPSession, 1);
716         session_init(SESSION(session));
717         SESSION(session)->type             = SESSION_IMAP;
718         SESSION(session)->server           = g_strdup(account->recv_server);
719         SESSION(session)->sock             = imap_sock;
720
721         SESSION(session)->destroy          = imap_session_destroy;
722
723         session->capability = NULL;
724
725         session->authenticated = FALSE;
726         session->mbox = NULL;
727         session->cmd_count = 0;
728
729         /* Only need to log in if the connection was not PREAUTH */
730         if (imap_greeting(session) != IMAP_SUCCESS) {
731                 session_destroy(SESSION(session));
732                 return NULL;
733         }
734
735 #if USE_OPENSSL
736         if (account->ssl_imap == SSL_STARTTLS && 
737             imap_has_capability(session, "STARTTLS")) {
738                 gint ok;
739
740                 ok = imap_cmd_starttls(session);
741                 if (ok != IMAP_SUCCESS) {
742                         log_warning(_("Can't start TLS session.\n"));
743                         session_destroy(SESSION(session));
744                         return NULL;
745                 }
746                 if (!ssl_init_socket_with_method(SESSION(session)->sock, SSL_METHOD_TLSv1)) {
747                         session_destroy(SESSION(session));
748                         return NULL;
749                 }
750
751                 imap_free_capabilities(session);
752                 session->authenticated = FALSE;
753                 session->uidplus = FALSE;
754                 session->cmd_count = 1;
755
756                 if (imap_greeting(session) != IMAP_SUCCESS) {
757                         session_destroy(SESSION(session));
758                         return NULL;
759                 }               
760         }
761 #endif
762         log_message("IMAP connection is %s-authenticated\n",
763                     (session->authenticated) ? "pre" : "un");
764
765         return session;
766 }
767
768 void imap_session_authenticate(IMAPSession *session, const PrefsAccount *account)
769 {
770         gchar *pass;
771
772         g_return_if_fail(account->userid != NULL);
773
774         pass = account->passwd;
775         if (!pass) {
776                 gchar *tmp_pass;
777                 tmp_pass = input_dialog_query_password(account->recv_server, account->userid);
778                 if (!tmp_pass)
779                         return;
780                 Xstrdup_a(pass, tmp_pass, {g_free(tmp_pass); return;});
781                 g_free(tmp_pass);
782         }
783
784         if (imap_auth(session, account->userid, pass, account->imap_auth_type) != IMAP_SUCCESS) {
785                 imap_cmd_logout(session);
786                 return;
787         }
788
789         session->authenticated = TRUE;
790 }
791
792 void imap_session_destroy(Session *session)
793 {
794         imap_free_capabilities(IMAP_SESSION(session));
795         g_free(IMAP_SESSION(session)->mbox);
796         sock_close(session->sock);
797         session->sock = NULL;
798 }
799
800 gchar *imap_fetch_msg(Folder *folder, FolderItem *item, gint uid)
801 {
802         gchar *path, *filename;
803         IMAPSession *session;
804         gint ok;
805
806         g_return_val_if_fail(folder != NULL, NULL);
807         g_return_val_if_fail(item != NULL, NULL);
808
809         path = folder_item_get_path(item);
810         if (!is_dir_exist(path))
811                 make_dir_hier(path);
812         filename = g_strconcat(path, G_DIR_SEPARATOR_S, itos(uid), NULL);
813         g_free(path);
814  
815         if (is_file_exist(filename)) {
816                 debug_print("message %d has been already cached.\n", uid);
817                 return filename;
818         }
819
820         session = imap_session_get(folder);
821         if (!session) {
822                 g_free(filename);
823                 return NULL;
824         }
825
826         ok = imap_select(session, IMAP_FOLDER(folder), item->path,
827                          NULL, NULL, NULL, NULL);
828         if (ok != IMAP_SUCCESS) {
829                 g_warning("can't select mailbox %s\n", item->path);
830                 g_free(filename);
831                 return NULL;
832         }
833
834         debug_print("getting message %d...\n", uid);
835         ok = imap_cmd_fetch(session, (guint32)uid, filename);
836
837         if (ok != IMAP_SUCCESS) {
838                 g_warning("can't fetch message %d\n", uid);
839                 g_free(filename);
840                 return NULL;
841         }
842
843         return filename;
844 }
845
846 gint imap_add_msg(Folder *folder, FolderItem *dest, const gchar *file, MsgFlags *flags)
847 {
848         gint ret;
849         GSList file_list;
850         MsgFileInfo fileinfo;
851
852         g_return_val_if_fail(file != NULL, -1);
853
854         fileinfo.msginfo = NULL;
855         fileinfo.file = (gchar *)file;
856         fileinfo.flags = flags;
857         file_list.data = &fileinfo;
858         file_list.next = NULL;
859
860         ret = imap_add_msgs(folder, dest, &file_list, NULL);
861         return ret;
862 }
863
864 gint imap_add_msgs(Folder *folder, FolderItem *dest, GSList *file_list,
865                    GRelation *relation)
866 {
867         gchar *destdir;
868         IMAPSession *session;
869         guint32 last_uid = 0;
870         GSList *cur;
871         MsgFileInfo *fileinfo;
872         gint ok;
873
874         g_return_val_if_fail(folder != NULL, -1);
875         g_return_val_if_fail(dest != NULL, -1);
876         g_return_val_if_fail(file_list != NULL, -1);
877
878         session = imap_session_get(folder);
879         if (!session) return -1;
880
881         destdir = imap_get_real_path(IMAP_FOLDER(folder), dest->path);
882
883         for (cur = file_list; cur != NULL; cur = cur->next) {
884                 IMAPFlags iflags = 0;
885                 guint32 new_uid = 0;
886
887                 fileinfo = (MsgFileInfo *)cur->data;
888
889                 if (fileinfo->flags) {
890                         if (MSG_IS_MARKED(*fileinfo->flags))
891                                 iflags |= IMAP_FLAG_FLAGGED;
892                         if (MSG_IS_REPLIED(*fileinfo->flags))
893                                 iflags |= IMAP_FLAG_ANSWERED;
894                         if (!MSG_IS_UNREAD(*fileinfo->flags))
895                                 iflags |= IMAP_FLAG_SEEN;
896                 }
897
898                 if (dest->stype == F_OUTBOX ||
899                     dest->stype == F_QUEUE  ||
900                     dest->stype == F_DRAFT  ||
901                     dest->stype == F_TRASH)
902                         iflags |= IMAP_FLAG_SEEN;
903
904                 ok = imap_cmd_append(session, destdir, fileinfo->file, iflags, 
905                                      &new_uid);
906
907                 if (ok != IMAP_SUCCESS) {
908                         g_warning("can't append message %s\n", fileinfo->file);
909                         g_free(destdir);
910                         return -1;
911                 }
912
913                 if (relation != NULL)
914                         g_relation_insert(relation, fileinfo->msginfo != NULL ? 
915                                           (gpointer) fileinfo->msginfo : (gpointer) fileinfo,
916                                           GINT_TO_POINTER(dest->last_num + 1));
917                 if (last_uid < new_uid)
918                         last_uid = new_uid;
919         }
920
921         g_free(destdir);
922
923         return last_uid;
924 }
925
926 static gint imap_do_copy_msgs(Folder *folder, FolderItem *dest, 
927                               MsgInfoList *msglist, GRelation *relation)
928 {
929         FolderItem *src;
930         gchar *destdir;
931         GSList *seq_list, *cur;
932         MsgInfo *msginfo;
933         IMAPSession *session;
934         gint ok = IMAP_SUCCESS;
935         GRelation *uid_mapping;
936         gint last_num = 0;
937         
938         g_return_val_if_fail(folder != NULL, -1);
939         g_return_val_if_fail(dest != NULL, -1);
940         g_return_val_if_fail(msglist != NULL, -1);
941
942         session = imap_session_get(folder);
943         if (!session) return -1;
944
945         msginfo = (MsgInfo *)msglist->data;
946
947         src = msginfo->folder;
948         if (src == dest) {
949                 g_warning("the src folder is identical to the dest.\n");
950                 return -1;
951         }
952
953         ok = imap_select(session, IMAP_FOLDER(folder), msginfo->folder->path,
954                          NULL, NULL, NULL, NULL);
955         if (ok != IMAP_SUCCESS)
956                 return ok;
957
958         destdir = imap_get_real_path(IMAP_FOLDER(folder), dest->path);
959         seq_list = imap_get_seq_set_from_msglist(msglist);
960         uid_mapping = g_relation_new(2);
961         g_relation_index(uid_mapping, 0, g_direct_hash, g_direct_equal);
962         
963         for (cur = seq_list; cur != NULL; cur = g_slist_next(cur)) {
964                 gchar *seq_set = (gchar *)cur->data;
965
966                 debug_print("Copying message %s%c[%s] to %s ...\n",
967                             src->path, G_DIR_SEPARATOR,
968                             seq_set, destdir);
969
970                 ok = imap_cmd_copy(session, seq_set, destdir, uid_mapping);
971                 if (ok != IMAP_SUCCESS) {
972                         g_relation_destroy(uid_mapping);
973                         imap_seq_set_free(seq_list);
974                         return -1;
975                 }
976         }
977
978         for (cur = msglist; cur != NULL; cur = g_slist_next(cur)) {
979                 MsgInfo *msginfo = (MsgInfo *)cur->data;
980                 GTuples *tuples;
981
982                 tuples = g_relation_select(uid_mapping, 
983                                            GINT_TO_POINTER(msginfo->msgnum),
984                                            0);
985                 if (tuples->len > 0) {
986                         gint num = GPOINTER_TO_INT(g_tuples_index(tuples, 0, 1));
987                         g_relation_insert(relation, msginfo,
988                                           GPOINTER_TO_INT(num));
989                         if (num > last_num)
990                                 last_num = num;
991                 } else
992                         g_relation_insert(relation, msginfo,
993                                           GPOINTER_TO_INT(0));
994                 g_tuples_destroy(tuples);
995         }
996         
997         imap_seq_set_free(seq_list);
998
999         g_free(destdir);
1000
1001         if (ok == IMAP_SUCCESS)
1002                 return last_num;
1003         else
1004                 return -1;
1005 }
1006
1007 gint imap_copy_msg(Folder *folder, FolderItem *dest, MsgInfo *msginfo)
1008 {
1009         GSList msglist;
1010
1011         g_return_val_if_fail(msginfo != NULL, -1);
1012
1013         msglist.data = msginfo;
1014         msglist.next = NULL;
1015
1016         return imap_copy_msgs(folder, dest, &msglist, NULL);
1017 }
1018
1019 gint imap_copy_msgs(Folder *folder, FolderItem *dest, 
1020                     MsgInfoList *msglist, GRelation *relation)
1021 {
1022         MsgInfo *msginfo;
1023         GSList *file_list;
1024         gint ret;
1025
1026         g_return_val_if_fail(folder != NULL, -1);
1027         g_return_val_if_fail(dest != NULL, -1);
1028         g_return_val_if_fail(msglist != NULL, -1);
1029
1030         msginfo = (MsgInfo *)msglist->data;
1031         g_return_val_if_fail(msginfo->folder != NULL, -1);
1032
1033         if (folder == msginfo->folder->folder)
1034                 return imap_do_copy_msgs(folder, dest, msglist, relation);
1035
1036         file_list = procmsg_get_message_file_list(msglist);
1037         g_return_val_if_fail(file_list != NULL, -1);
1038
1039         ret = imap_add_msgs(folder, dest, file_list, relation);
1040
1041         procmsg_message_file_list_free(file_list);
1042
1043         return ret;
1044 }
1045
1046 gint imap_remove_msg(Folder *folder, FolderItem *item, gint uid)
1047 {
1048         gint ok;
1049         IMAPSession *session;
1050         gchar *dir;
1051         MsgNumberList numlist;
1052         
1053         g_return_val_if_fail(folder != NULL, -1);
1054         g_return_val_if_fail(FOLDER_CLASS(folder) == &imap_class, -1);
1055         g_return_val_if_fail(item != NULL, -1);
1056
1057         session = imap_session_get(folder);
1058         if (!session) return -1;
1059
1060         ok = imap_select(session, IMAP_FOLDER(folder), item->path,
1061                          NULL, NULL, NULL, NULL);
1062         if (ok != IMAP_SUCCESS)
1063                 return ok;
1064
1065         numlist.next = NULL;
1066         numlist.data = GINT_TO_POINTER(uid);
1067         
1068         ok = imap_set_message_flags
1069                 (IMAP_SESSION(REMOTE_FOLDER(folder)->session),
1070                 &numlist, IMAP_FLAG_DELETED, TRUE);
1071         if (ok != IMAP_SUCCESS) {
1072                 log_warning(_("can't set deleted flags: %d\n"), uid);
1073                 return ok;
1074         }
1075
1076         if (!session->uidplus) {
1077                 ok = imap_cmd_expunge(session, NULL);
1078         } else {
1079                 gchar *uidstr;
1080
1081                 uidstr = g_strdup_printf("%u", uid);
1082                 ok = imap_cmd_expunge(session, uidstr);
1083                 g_free(uidstr);
1084         }
1085         if (ok != IMAP_SUCCESS) {
1086                 log_warning(_("can't expunge\n"));
1087                 return ok;
1088         }
1089
1090         IMAP_FOLDER_ITEM(item)->uid_list = g_slist_remove(
1091             IMAP_FOLDER_ITEM(item)->uid_list, numlist.data);
1092         dir = folder_item_get_path(item);
1093         if (is_dir_exist(dir))
1094                 remove_numbered_files(dir, uid, uid);
1095         g_free(dir);
1096
1097         return IMAP_SUCCESS;
1098 }
1099
1100 gint imap_remove_all_msg(Folder *folder, FolderItem *item)
1101 {
1102         gint ok;
1103         IMAPSession *session;
1104         gchar *dir;
1105
1106         g_return_val_if_fail(folder != NULL, -1);
1107         g_return_val_if_fail(item != NULL, -1);
1108
1109         session = imap_session_get(folder);
1110         if (!session) return -1;
1111
1112         ok = imap_select(session, IMAP_FOLDER(folder), item->path,
1113                          NULL, NULL, NULL, NULL);
1114         if (ok != IMAP_SUCCESS)
1115                 return ok;
1116
1117         imap_gen_send(session, "STORE 1:* +FLAGS.SILENT (\\Deleted)");
1118         ok = imap_cmd_ok(session, NULL);
1119         if (ok != IMAP_SUCCESS) {
1120                 log_warning(_("can't set deleted flags: 1:*\n"));
1121                 return ok;
1122         }
1123
1124         ok = imap_cmd_expunge(session, NULL);
1125         if (ok != IMAP_SUCCESS) {
1126                 log_warning(_("can't expunge\n"));
1127                 return ok;
1128         }
1129
1130         dir = folder_item_get_path(item);
1131         if (is_dir_exist(dir))
1132                 remove_all_numbered_files(dir);
1133         g_free(dir);
1134
1135         return IMAP_SUCCESS;
1136 }
1137
1138 gboolean imap_is_msg_changed(Folder *folder, FolderItem *item, MsgInfo *msginfo)
1139 {
1140         /* TODO: properly implement this method */
1141         return FALSE;
1142 }
1143
1144 gint imap_close(Folder *folder, FolderItem *item)
1145 {
1146         gint ok;
1147         IMAPSession *session;
1148
1149         g_return_val_if_fail(folder != NULL, -1);
1150         g_return_val_if_fail(item != NULL, -1);
1151         g_return_val_if_fail(item->path != NULL, -1);
1152
1153         session = imap_session_get(folder);
1154         if (!session) return -1;
1155
1156         if (session->mbox) {
1157                 if (strcmp2(session->mbox, item->path) != 0) return -1;
1158
1159                 ok = imap_cmd_close(session);
1160                 if (ok != IMAP_SUCCESS)
1161                         log_warning(_("can't close folder\n"));
1162
1163                 g_free(session->mbox);
1164                 session->mbox = NULL;
1165
1166                 return ok;
1167         }
1168
1169         return 0;
1170 }
1171
1172 gint imap_scan_tree(Folder *folder)
1173 {
1174         FolderItem *item = NULL;
1175         IMAPSession *session;
1176         gchar *root_folder = NULL;
1177
1178         g_return_val_if_fail(folder != NULL, -1);
1179         g_return_val_if_fail(folder->account != NULL, -1);
1180
1181         session = imap_session_get(folder);
1182         if (!session) {
1183                 if (!folder->node) {
1184                         folder_tree_destroy(folder);
1185                         item = folder_item_new(folder, folder->name, NULL);
1186                         item->folder = folder;
1187                         folder->node = item->node = g_node_new(item);
1188                 }
1189                 return -1;
1190         }
1191
1192         if (folder->account->imap_dir && *folder->account->imap_dir) {
1193                 gchar *real_path;
1194
1195                 Xstrdup_a(root_folder, folder->account->imap_dir, return -1);
1196                 strtailchomp(root_folder, '/');
1197                 extract_quote(root_folder, '"');
1198                 real_path = imap_get_real_path
1199                         (IMAP_FOLDER(folder), root_folder);
1200                 debug_print("IMAP root directory: %s\n", real_path);
1201                 if (imap_status(session, IMAP_FOLDER(folder), root_folder,
1202                                     NULL, NULL, NULL, NULL, NULL)
1203                     != IMAP_SUCCESS) {
1204                         if (imap_cmd_create(session, real_path)
1205                             != IMAP_SUCCESS) {
1206                                 log_warning(_("can't create root folder %s\n"),
1207                                             real_path);
1208                                 g_free(real_path);
1209                                 return -1;
1210                         }
1211                 }
1212                 g_free(real_path);
1213         }
1214
1215         if (folder->node)
1216                 item = FOLDER_ITEM(folder->node->data);
1217         if (!item || ((item->path || root_folder) &&
1218                       strcmp2(item->path, root_folder) != 0)) {
1219                 folder_tree_destroy(folder);
1220                 item = folder_item_new(folder, folder->name, root_folder);
1221                 item->folder = folder;
1222                 folder->node = item->node = g_node_new(item);
1223         }
1224
1225         imap_scan_tree_recursive(session, FOLDER_ITEM(folder->node->data));
1226         imap_create_missing_folders(folder);
1227
1228         return 0;
1229 }
1230
1231 static gint imap_scan_tree_recursive(IMAPSession *session, FolderItem *item)
1232 {
1233         Folder *folder;
1234         IMAPFolder *imapfolder;
1235         FolderItem *new_item;
1236         GSList *item_list, *cur;
1237         GNode *node;
1238         gchar *real_path;
1239         gchar *wildcard_path;
1240         gchar separator;
1241         gchar wildcard[3];
1242
1243         g_return_val_if_fail(item != NULL, -1);
1244         g_return_val_if_fail(item->folder != NULL, -1);
1245         g_return_val_if_fail(item->no_sub == FALSE, -1);
1246
1247         folder = item->folder;
1248         imapfolder = IMAP_FOLDER(folder);
1249
1250         separator = imap_get_path_separator(imapfolder, item->path);
1251
1252         if (folder->ui_func)
1253                 folder->ui_func(folder, item, folder->ui_func_data);
1254
1255         if (item->path) {
1256                 wildcard[0] = separator;
1257                 wildcard[1] = '%';
1258                 wildcard[2] = '\0';
1259                 real_path = imap_get_real_path(imapfolder, item->path);
1260         } else {
1261                 wildcard[0] = '%';
1262                 wildcard[1] = '\0';
1263                 real_path = g_strdup("");
1264         }
1265
1266         Xstrcat_a(wildcard_path, real_path, wildcard,
1267                   {g_free(real_path); return IMAP_ERROR;});
1268         QUOTE_IF_REQUIRED(wildcard_path, wildcard_path);
1269
1270         imap_gen_send(session, "LIST \"\" %s",
1271                       wildcard_path);
1272
1273         strtailchomp(real_path, separator);
1274         item_list = imap_parse_list(imapfolder, session, real_path, NULL);
1275         g_free(real_path);
1276
1277         node = item->node->children;
1278         while (node != NULL) {
1279                 FolderItem *old_item = FOLDER_ITEM(node->data);
1280                 GNode *next = node->next;
1281
1282                 new_item = NULL;
1283                 for (cur = item_list; cur != NULL; cur = cur->next) {
1284                         FolderItem *cur_item = FOLDER_ITEM(cur->data);
1285                         if (!strcmp2(old_item->path, cur_item->path)) {
1286                                 new_item = cur_item;
1287                                 break;
1288                         }
1289                 }
1290                 if (!new_item) {
1291                         debug_print("folder '%s' not found. removing...\n",
1292                                     old_item->path);
1293                         folder_item_remove(old_item);
1294                 } else {
1295                         old_item->no_sub = new_item->no_sub;
1296                         old_item->no_select = new_item->no_select;
1297                         if (old_item->no_sub == TRUE && node->children) {
1298                                 debug_print("folder '%s' doesn't have "
1299                                             "subfolders. removing...\n",
1300                                             old_item->path);
1301                                 folder_item_remove_children(old_item);
1302                         }
1303                 }
1304
1305                 node = next;
1306         }
1307
1308         for (cur = item_list; cur != NULL; cur = cur->next) {
1309                 FolderItem *cur_item = FOLDER_ITEM(cur->data);
1310                 new_item = NULL;
1311                 for (node = item->node->children; node != NULL;
1312                      node = node->next) {
1313                         if (!strcmp2(FOLDER_ITEM(node->data)->path,
1314                                      cur_item->path)) {
1315                                 new_item = FOLDER_ITEM(node->data);
1316                                 folder_item_destroy(cur_item);
1317                                 cur_item = NULL;
1318                                 break;
1319                         }
1320                 }
1321                 if (!new_item) {
1322                         new_item = cur_item;
1323                         debug_print("new folder '%s' found.\n", new_item->path);
1324                         folder_item_append(item, new_item);
1325                 }
1326
1327                 if (!strcmp(new_item->path, "INBOX")) {
1328                         new_item->stype = F_INBOX;
1329                         folder->inbox = new_item;
1330                 } else if (!item->parent || item->stype == F_INBOX) {
1331                         gchar *base;
1332
1333                         base = g_basename(new_item->path);
1334
1335                         if (!folder->outbox && !strcasecmp(base, "Sent")) {
1336                                 new_item->stype = F_OUTBOX;
1337                                 folder->outbox = new_item;
1338                         } else if (!folder->draft && !strcasecmp(base, "Drafts")) {
1339                                 new_item->stype = F_DRAFT;
1340                                 folder->draft = new_item;
1341                         } else if (!folder->queue && !strcasecmp(base, "Queue")) {
1342                                 new_item->stype = F_QUEUE;
1343                                 folder->queue = new_item;
1344                         } else if (!folder->trash && !strcasecmp(base, "Trash")) {
1345                                 new_item->stype = F_TRASH;
1346                                 folder->trash = new_item;
1347                         }
1348                 }
1349
1350                 if (new_item->no_sub == FALSE)
1351                         imap_scan_tree_recursive(session, new_item);
1352         }
1353
1354         g_slist_free(item_list);
1355
1356         return IMAP_SUCCESS;
1357 }
1358
1359 static GSList *imap_parse_list(IMAPFolder *folder, IMAPSession *session,
1360                                const gchar *real_path, gchar *separator)
1361 {
1362         gchar buf[IMAPBUFSIZE];
1363         gchar flags[256];
1364         gchar separator_str[16];
1365         gchar *p;
1366         gchar *name;
1367         gchar *loc_name, *loc_path;
1368         GSList *item_list = NULL;
1369         GString *str;
1370         FolderItem *new_item;
1371
1372         debug_print("getting list of %s ...\n",
1373                     *real_path ? real_path : "\"\"");
1374
1375         str = g_string_new(NULL);
1376
1377         for (;;) {
1378                 if (sock_gets(SESSION(session)->sock, buf, sizeof(buf)) <= 0) {
1379                         log_warning(_("error occurred while getting LIST.\n"));
1380                         break;
1381                 }
1382                 strretchomp(buf);
1383                 if (buf[0] != '*' || buf[1] != ' ') {
1384                         log_print("IMAP4< %s\n", buf);
1385                         if (sscanf(buf, "%*d %16s", buf) < 1 ||
1386                             strcmp(buf, "OK") != 0)
1387                                 log_warning(_("error occurred while getting LIST.\n"));
1388                                 
1389                         break;
1390                 }
1391                 debug_print("IMAP4< %s\n", buf);
1392
1393                 g_string_assign(str, buf);
1394                 p = str->str + 2;
1395                 if (strncmp(p, "LIST ", 5) != 0) continue;
1396                 p += 5;
1397
1398                 if (*p != '(') continue;
1399                 p++;
1400                 p = strchr_cpy(p, ')', flags, sizeof(flags));
1401                 if (!p) continue;
1402                 while (*p == ' ') p++;
1403
1404                 p = strchr_cpy(p, ' ', separator_str, sizeof(separator_str));
1405                 if (!p) continue;
1406                 extract_quote(separator_str, '"');
1407                 if (!strcmp(separator_str, "NIL"))
1408                         separator_str[0] = '\0';
1409                 if (separator)
1410                         *separator = separator_str[0];
1411
1412                 buf[0] = '\0';
1413                 while (*p == ' ') p++;
1414                 if (*p == '{' || *p == '"')
1415                         p = imap_parse_atom(SESSION(session)->sock, p,
1416                                             buf, sizeof(buf), str);
1417                 else
1418                         strncpy2(buf, p, sizeof(buf));
1419                 strtailchomp(buf, separator_str[0]);
1420                 if (buf[0] == '\0') continue;
1421                 if (!strcmp(buf, real_path)) continue;
1422
1423                 if (separator_str[0] != '\0')
1424                         subst_char(buf, separator_str[0], '/');
1425                 name = g_basename(buf);
1426                 if (name[0] == '.') continue;
1427
1428                 loc_name = imap_modified_utf7_to_locale(name);
1429                 loc_path = imap_modified_utf7_to_locale(buf);
1430                 new_item = folder_item_new(FOLDER(folder), loc_name, loc_path);
1431                 if (strcasestr(flags, "\\Noinferiors") != NULL)
1432                         new_item->no_sub = TRUE;
1433                 if (strcmp(buf, "INBOX") != 0 &&
1434                     strcasestr(flags, "\\Noselect") != NULL)
1435                         new_item->no_select = TRUE;
1436
1437                 item_list = g_slist_append(item_list, new_item);
1438
1439                 debug_print("folder '%s' found.\n", loc_path);
1440                 g_free(loc_path);
1441                 g_free(loc_name);
1442         }
1443
1444         g_string_free(str, TRUE);
1445
1446         return item_list;
1447 }
1448
1449 gint imap_create_tree(Folder *folder)
1450 {
1451         g_return_val_if_fail(folder != NULL, -1);
1452         g_return_val_if_fail(folder->node != NULL, -1);
1453         g_return_val_if_fail(folder->node->data != NULL, -1);
1454         g_return_val_if_fail(folder->account != NULL, -1);
1455
1456         imap_scan_tree(folder);
1457         imap_create_missing_folders(folder);
1458
1459         return 0;
1460 }
1461
1462 static void imap_create_missing_folders(Folder *folder)
1463 {
1464         g_return_if_fail(folder != NULL);
1465
1466         if (!folder->inbox)
1467                 folder->inbox = imap_create_special_folder
1468                         (folder, F_INBOX, "INBOX");
1469 #if 0
1470         if (!folder->outbox)
1471                 folder->outbox = imap_create_special_folder
1472                         (folder, F_OUTBOX, "Sent");
1473         if (!folder->draft)
1474                 folder->draft = imap_create_special_folder
1475                         (folder, F_DRAFT, "Drafts");
1476         if (!folder->queue)
1477                 folder->queue = imap_create_special_folder
1478                         (folder, F_QUEUE, "Queue");
1479 #endif
1480         if (!folder->trash)
1481                 folder->trash = imap_create_special_folder
1482                         (folder, F_TRASH, "Trash");
1483 }
1484
1485 static FolderItem *imap_create_special_folder(Folder *folder,
1486                                               SpecialFolderItemType stype,
1487                                               const gchar *name)
1488 {
1489         FolderItem *item;
1490         FolderItem *new_item;
1491
1492         g_return_val_if_fail(folder != NULL, NULL);
1493         g_return_val_if_fail(folder->node != NULL, NULL);
1494         g_return_val_if_fail(folder->node->data != NULL, NULL);
1495         g_return_val_if_fail(folder->account != NULL, NULL);
1496         g_return_val_if_fail(name != NULL, NULL);
1497
1498         item = FOLDER_ITEM(folder->node->data);
1499         new_item = imap_create_folder(folder, item, name);
1500
1501         if (!new_item) {
1502                 g_warning("Can't create '%s'\n", name);
1503                 if (!folder->inbox) return NULL;
1504
1505                 new_item = imap_create_folder(folder, folder->inbox, name);
1506                 if (!new_item)
1507                         g_warning("Can't create '%s' under INBOX\n", name);
1508                 else
1509                         new_item->stype = stype;
1510         } else
1511                 new_item->stype = stype;
1512
1513         return new_item;
1514 }
1515
1516 static gchar *imap_folder_get_path(Folder *folder)
1517 {
1518         gchar *folder_path;
1519
1520         g_return_val_if_fail(folder != NULL, NULL);
1521         g_return_val_if_fail(folder->account != NULL, NULL);
1522
1523         folder_path = g_strconcat(get_imap_cache_dir(),
1524                                   G_DIR_SEPARATOR_S,
1525                                   folder->account->recv_server,
1526                                   G_DIR_SEPARATOR_S,
1527                                   folder->account->userid,
1528                                   NULL);
1529
1530         return folder_path;
1531 }
1532
1533 static gchar *imap_item_get_path(Folder *folder, FolderItem *item)
1534 {
1535         gchar *folder_path, *path;
1536
1537         g_return_val_if_fail(folder != NULL, NULL);
1538         g_return_val_if_fail(item != NULL, NULL);
1539         folder_path = imap_folder_get_path(folder);
1540
1541         g_return_val_if_fail(folder_path != NULL, NULL);
1542         if (folder_path[0] == G_DIR_SEPARATOR) {
1543                 if (item->path)
1544                         path = g_strconcat(folder_path, G_DIR_SEPARATOR_S,
1545                                            item->path, NULL);
1546                 else
1547                         path = g_strdup(folder_path);
1548         } else {
1549                 if (item->path)
1550                         path = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S,
1551                                            folder_path, G_DIR_SEPARATOR_S,
1552                                            item->path, NULL);
1553                 else
1554                         path = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S,
1555                                            folder_path, NULL);
1556         }
1557         g_free(folder_path);
1558
1559         return path;
1560 }
1561
1562 FolderItem *imap_create_folder(Folder *folder, FolderItem *parent,
1563                                const gchar *name)
1564 {
1565         gchar *dirpath, *imap_path;
1566         IMAPSession *session;
1567         FolderItem *new_item;
1568         gchar separator;
1569         gchar *new_name;
1570         const gchar *p;
1571         gint ok;
1572
1573         g_return_val_if_fail(folder != NULL, NULL);
1574         g_return_val_if_fail(folder->account != NULL, NULL);
1575         g_return_val_if_fail(parent != NULL, NULL);
1576         g_return_val_if_fail(name != NULL, NULL);
1577
1578         session = imap_session_get(folder);
1579         if (!session) return NULL;
1580
1581         if (!parent->parent && strcmp(name, "INBOX") == 0)
1582                 dirpath = g_strdup(name);
1583         else if (parent->path)
1584                 dirpath = g_strconcat(parent->path, "/", name, NULL);
1585         else if ((p = strchr(name, '/')) != NULL && *(p + 1) != '\0')
1586                 dirpath = g_strdup(name);
1587         else if (folder->account->imap_dir && *folder->account->imap_dir) {
1588                 gchar *imap_dir;
1589
1590                 Xstrdup_a(imap_dir, folder->account->imap_dir, return NULL);
1591                 strtailchomp(imap_dir, '/');
1592                 dirpath = g_strconcat(imap_dir, "/", name, NULL);
1593         } else
1594                 dirpath = g_strdup(name);
1595
1596         /* keep trailing directory separator to create a folder that contains
1597            sub folder */
1598         imap_path = imap_locale_to_modified_utf7(dirpath);
1599         strtailchomp(dirpath, '/');
1600         Xstrdup_a(new_name, name, {g_free(dirpath); return NULL;});
1601         strtailchomp(new_name, '/');
1602         separator = imap_get_path_separator(IMAP_FOLDER(folder), imap_path);
1603         imap_path_separator_subst(imap_path, separator);
1604         subst_char(new_name, '/', separator);
1605
1606         if (strcmp(name, "INBOX") != 0) {
1607                 GPtrArray *argbuf;
1608                 gint i;
1609                 gboolean exist = FALSE;
1610
1611                 argbuf = g_ptr_array_new();
1612                 ok = imap_cmd_list(session, NULL, imap_path,
1613                                    argbuf);
1614                 if (ok != IMAP_SUCCESS) {
1615                         log_warning(_("can't create mailbox: LIST failed\n"));
1616                         g_free(imap_path);
1617                         g_free(dirpath);
1618                         ptr_array_free_strings(argbuf);
1619                         g_ptr_array_free(argbuf, TRUE);
1620                         return NULL;
1621                 }
1622
1623                 for (i = 0; i < argbuf->len; i++) {
1624                         gchar *str;
1625                         str = g_ptr_array_index(argbuf, i);
1626                         if (!strncmp(str, "LIST ", 5)) {
1627                                 exist = TRUE;
1628                                 break;
1629                         }
1630                 }
1631                 ptr_array_free_strings(argbuf);
1632                 g_ptr_array_free(argbuf, TRUE);
1633
1634                 if (!exist) {
1635                         ok = imap_cmd_create(session, imap_path);
1636                         if (ok != IMAP_SUCCESS) {
1637                                 log_warning(_("can't create mailbox\n"));
1638                                 g_free(imap_path);
1639                                 g_free(dirpath);
1640                                 return NULL;
1641                         }
1642                 }
1643         }
1644
1645         new_item = folder_item_new(folder, new_name, dirpath);
1646         folder_item_append(parent, new_item);
1647         g_free(imap_path);
1648         g_free(dirpath);
1649
1650         dirpath = folder_item_get_path(new_item);
1651         if (!is_dir_exist(dirpath))
1652                 make_dir_hier(dirpath);
1653         g_free(dirpath);
1654
1655         return new_item;
1656 }
1657
1658 gint imap_rename_folder(Folder *folder, FolderItem *item, const gchar *name)
1659 {
1660         gchar *dirpath;
1661         gchar *newpath;
1662         gchar *real_oldpath;
1663         gchar *real_newpath;
1664         gchar *paths[2];
1665         gchar *old_cache_dir;
1666         gchar *new_cache_dir;
1667         IMAPSession *session;
1668         gchar separator;
1669         gint ok;
1670         gint exists, recent, unseen;
1671         guint32 uid_validity;
1672
1673         g_return_val_if_fail(folder != NULL, -1);
1674         g_return_val_if_fail(item != NULL, -1);
1675         g_return_val_if_fail(item->path != NULL, -1);
1676         g_return_val_if_fail(name != NULL, -1);
1677
1678         session = imap_session_get(folder);
1679         if (!session) return -1;
1680
1681         real_oldpath = imap_get_real_path(IMAP_FOLDER(folder), item->path);
1682
1683         g_free(session->mbox);
1684         session->mbox = NULL;
1685         ok = imap_cmd_examine(session, "INBOX",
1686                               &exists, &recent, &unseen, &uid_validity);
1687         if (ok != IMAP_SUCCESS) {
1688                 g_free(real_oldpath);
1689                 return -1;
1690         }
1691
1692         separator = imap_get_path_separator(IMAP_FOLDER(folder), item->path);
1693         if (strchr(item->path, G_DIR_SEPARATOR)) {
1694                 dirpath = g_dirname(item->path);
1695                 newpath = g_strconcat(dirpath, G_DIR_SEPARATOR_S, name, NULL);
1696                 g_free(dirpath);
1697         } else
1698                 newpath = g_strdup(name);
1699
1700         real_newpath = imap_locale_to_modified_utf7(newpath);
1701         imap_path_separator_subst(real_newpath, separator);
1702
1703         ok = imap_cmd_rename(session, real_oldpath, real_newpath);
1704         if (ok != IMAP_SUCCESS) {
1705                 log_warning(_("can't rename mailbox: %s to %s\n"),
1706                             real_oldpath, real_newpath);
1707                 g_free(real_oldpath);
1708                 g_free(newpath);
1709                 g_free(real_newpath);
1710                 return -1;
1711         }
1712
1713         g_free(item->name);
1714         item->name = g_strdup(name);
1715
1716         old_cache_dir = folder_item_get_path(item);
1717
1718         paths[0] = g_strdup(item->path);
1719         paths[1] = newpath;
1720         g_node_traverse(item->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
1721                         imap_rename_folder_func, paths);
1722
1723         if (is_dir_exist(old_cache_dir)) {
1724                 new_cache_dir = folder_item_get_path(item);
1725                 if (rename(old_cache_dir, new_cache_dir) < 0) {
1726                         FILE_OP_ERROR(old_cache_dir, "rename");
1727                 }
1728                 g_free(new_cache_dir);
1729         }
1730
1731         g_free(old_cache_dir);
1732         g_free(paths[0]);
1733         g_free(newpath);
1734         g_free(real_oldpath);
1735         g_free(real_newpath);
1736
1737         return 0;
1738 }
1739
1740 gint imap_remove_folder(Folder *folder, FolderItem *item)
1741 {
1742         gint ok;
1743         IMAPSession *session;
1744         gchar *path;
1745         gchar *cache_dir;
1746         gint exists, recent, unseen;
1747         guint32 uid_validity;
1748
1749         g_return_val_if_fail(folder != NULL, -1);
1750         g_return_val_if_fail(item != NULL, -1);
1751         g_return_val_if_fail(item->path != NULL, -1);
1752
1753         session = imap_session_get(folder);
1754         if (!session) return -1;
1755
1756         path = imap_get_real_path(IMAP_FOLDER(folder), item->path);
1757
1758         ok = imap_cmd_examine(session, "INBOX",
1759                               &exists, &recent, &unseen, &uid_validity);
1760         if (ok != IMAP_SUCCESS) {
1761                 g_free(path);
1762                 return -1;
1763         }
1764
1765         ok = imap_cmd_delete(session, path);
1766         if (ok != IMAP_SUCCESS) {
1767                 log_warning(_("can't delete mailbox\n"));
1768                 g_free(path);
1769                 return -1;
1770         }
1771
1772         g_free(path);
1773         cache_dir = folder_item_get_path(item);
1774         if (is_dir_exist(cache_dir) && remove_dir_recursive(cache_dir) < 0)
1775                 g_warning("can't remove directory '%s'\n", cache_dir);
1776         g_free(cache_dir);
1777         folder_item_remove(item);
1778
1779         return 0;
1780 }
1781
1782 static GSList *imap_get_uncached_messages(IMAPSession *session,
1783                                           FolderItem *item,
1784                                           MsgNumberList *numlist)
1785 {
1786         gchar *tmp;
1787         GSList *newlist = NULL;
1788         GSList *llast = NULL;
1789         GString *str;
1790         MsgInfo *msginfo;
1791         GSList *seq_list, *cur;
1792         IMAPSet imapset;
1793
1794         g_return_val_if_fail(session != NULL, NULL);
1795         g_return_val_if_fail(item != NULL, NULL);
1796         g_return_val_if_fail(item->folder != NULL, NULL);
1797         g_return_val_if_fail(FOLDER_CLASS(item->folder) == &imap_class, NULL);
1798
1799         seq_list = imap_get_seq_set_from_numlist(numlist);
1800         for (cur = seq_list; cur != NULL; cur = g_slist_next(cur)) {
1801                 imapset = cur->data;
1802
1803                 if (imap_cmd_envelope(session, imapset)
1804                     != IMAP_SUCCESS) {
1805                         log_warning(_("can't get envelope\n"));
1806                         continue;
1807                 }
1808
1809                 str = g_string_new(NULL);
1810
1811                 for (;;) {
1812                         if ((tmp = sock_getline(SESSION(session)->sock)) == NULL) {
1813                                 log_warning(_("error occurred while getting envelope.\n"));
1814                                 g_string_free(str, TRUE);
1815                                 break;
1816                         }
1817                         strretchomp(tmp);
1818                         if (tmp[0] != '*' || tmp[1] != ' ') {
1819                                 log_print("IMAP4< %s\n", tmp);
1820                                 g_free(tmp);
1821                                 break;
1822                         }
1823                         if (strstr(tmp, "FETCH") == NULL) {
1824                                 log_print("IMAP4< %s\n", tmp);
1825                                 g_free(tmp);
1826                                 continue;
1827                         }
1828                         log_print("IMAP4< %s\n", tmp);
1829                         g_string_assign(str, tmp);
1830                         g_free(tmp);
1831
1832                         msginfo = imap_parse_envelope
1833                                 (SESSION(session)->sock, item, str);
1834                         if (!msginfo) {
1835                                 log_warning(_("can't parse envelope: %s\n"), str->str);
1836                                 continue;
1837                         }
1838                         if (item->stype == F_QUEUE) {
1839                                 MSG_SET_TMP_FLAGS(msginfo->flags, MSG_QUEUED);
1840                         } else if (item->stype == F_DRAFT) {
1841                                 MSG_SET_TMP_FLAGS(msginfo->flags, MSG_DRAFT);
1842                         }
1843
1844                         msginfo->folder = item;
1845
1846                         if (!newlist)
1847                                 llast = newlist = g_slist_append(newlist, msginfo);
1848                         else {
1849                                 llast = g_slist_append(llast, msginfo);
1850                                 llast = llast->next;
1851                         }
1852                 }
1853
1854                 g_string_free(str, TRUE);
1855         }
1856         imap_seq_set_free(seq_list);
1857
1858         return newlist;
1859 }
1860
1861 static void imap_delete_all_cached_messages(FolderItem *item)
1862 {
1863         gchar *dir;
1864
1865         g_return_if_fail(item != NULL);
1866         g_return_if_fail(item->folder != NULL);
1867         g_return_if_fail(FOLDER_CLASS(item->folder) == &imap_class);
1868
1869         debug_print("Deleting all cached messages...\n");
1870
1871         dir = folder_item_get_path(item);
1872         if (is_dir_exist(dir))
1873                 remove_all_numbered_files(dir);
1874         g_free(dir);
1875
1876         debug_print("done.\n");
1877 }
1878
1879 #if USE_OPENSSL
1880 static SockInfo *imap_open_tunnel(const gchar *server,
1881                            const gchar *tunnelcmd,
1882                            SSLType ssl_type)
1883 #else
1884 static SockInfo *imap_open_tunnel(const gchar *server,
1885                            const gchar *tunnelcmd)
1886 #endif
1887 {
1888         SockInfo *sock;
1889
1890         if ((sock = sock_connect_cmd(server, tunnelcmd)) == NULL) {
1891                 log_warning(_("Can't establish IMAP4 session with: %s\n"),
1892                             server);
1893                 return NULL;
1894         }
1895 #if USE_OPENSSL
1896         return imap_init_sock(sock, ssl_type);
1897 #else
1898         return imap_init_sock(sock);
1899 #endif
1900 }
1901
1902
1903 #if USE_OPENSSL
1904 static SockInfo *imap_open(const gchar *server, gushort port,
1905                            SSLType ssl_type)
1906 #else
1907 static SockInfo *imap_open(const gchar *server, gushort port)
1908 #endif
1909 {
1910         SockInfo *sock;
1911
1912         if ((sock = sock_connect(server, port)) == NULL) {
1913                 log_warning(_("Can't connect to IMAP4 server: %s:%d\n"),
1914                             server, port);
1915                 return NULL;
1916         }
1917
1918 #if USE_OPENSSL
1919         if (ssl_type == SSL_TUNNEL && !ssl_init_socket(sock)) {
1920                 log_warning(_("Can't establish IMAP4 session with: %s:%d\n"),
1921                             server, port);
1922                 sock_close(sock);
1923                 return NULL;
1924         }
1925 #endif
1926         return sock;
1927 }
1928
1929 #if USE_OPENSSL
1930 static SockInfo *imap_init_sock(SockInfo *sock, SSLType ssl_type)
1931 #else
1932 static SockInfo *imap_init_sock(SockInfo *sock)
1933 #endif
1934 {
1935
1936         return sock;
1937 }
1938
1939 static GList *imap_parse_namespace_str(gchar *str)
1940 {
1941         gchar *p = str;
1942         gchar *name;
1943         gchar *separator;
1944         IMAPNameSpace *namespace;
1945         GList *ns_list = NULL;
1946
1947         while (*p != '\0') {
1948                 /* parse ("#foo" "/") */
1949
1950                 while (*p && *p != '(') p++;
1951                 if (*p == '\0') break;
1952                 p++;
1953
1954                 while (*p && *p != '"') p++;
1955                 if (*p == '\0') break;
1956                 p++;
1957                 name = p;
1958
1959                 while (*p && *p != '"') p++;
1960                 if (*p == '\0') break;
1961                 *p = '\0';
1962                 p++;
1963
1964                 while (*p && isspace(*p)) p++;
1965                 if (*p == '\0') break;
1966                 if (strncmp(p, "NIL", 3) == 0)
1967                         separator = NULL;
1968                 else if (*p == '"') {
1969                         p++;
1970                         separator = p;
1971                         while (*p && *p != '"') p++;
1972                         if (*p == '\0') break;
1973                         *p = '\0';
1974                         p++;
1975                 } else break;
1976
1977                 while (*p && *p != ')') p++;
1978                 if (*p == '\0') break;
1979                 p++;
1980
1981                 namespace = g_new(IMAPNameSpace, 1);
1982                 namespace->name = g_strdup(name);
1983                 namespace->separator = separator ? separator[0] : '\0';
1984                 ns_list = g_list_append(ns_list, namespace);
1985         }
1986
1987         return ns_list;
1988 }
1989
1990 static void imap_parse_namespace(IMAPSession *session, IMAPFolder *folder)
1991 {
1992         gchar *ns_str;
1993         gchar **str_array;
1994
1995         g_return_if_fail(session != NULL);
1996         g_return_if_fail(folder != NULL);
1997
1998         if (folder->ns_personal != NULL ||
1999             folder->ns_others   != NULL ||
2000             folder->ns_shared   != NULL)
2001                 return;
2002
2003         if (!imap_has_capability(session, "NAMESPACE")) {
2004                 imap_get_namespace_by_list(session, folder);
2005                 return;
2006         }
2007         
2008         if (imap_cmd_namespace(session, &ns_str)
2009             != IMAP_SUCCESS) {
2010                 log_warning(_("can't get namespace\n"));
2011                 return;
2012         }
2013
2014         str_array = strsplit_parenthesis(ns_str, '(', ')', 3);
2015         if (str_array == NULL) {
2016                 g_free(ns_str);
2017                 imap_get_namespace_by_list(session, folder);
2018                 return;
2019         }
2020         if (str_array[0])
2021                 folder->ns_personal = imap_parse_namespace_str(str_array[0]);
2022         if (str_array[0] && str_array[1])
2023                 folder->ns_others = imap_parse_namespace_str(str_array[1]);
2024         if (str_array[0] && str_array[1] && str_array[2])
2025                 folder->ns_shared = imap_parse_namespace_str(str_array[2]);
2026         g_strfreev(str_array);
2027         g_free(ns_str);
2028 }
2029
2030 static void imap_get_namespace_by_list(IMAPSession *session, IMAPFolder *folder)
2031 {
2032         GSList *item_list, *cur;
2033         gchar separator = '\0';
2034         IMAPNameSpace *namespace;
2035
2036         g_return_if_fail(session != NULL);
2037         g_return_if_fail(folder != NULL);
2038
2039         if (folder->ns_personal != NULL ||
2040             folder->ns_others   != NULL ||
2041             folder->ns_shared   != NULL)
2042                 return;
2043
2044         imap_gen_send(session, "LIST \"\" \"\"");
2045         item_list = imap_parse_list(folder, session, "", &separator);
2046         for (cur = item_list; cur != NULL; cur = cur->next)
2047                 folder_item_destroy(FOLDER_ITEM(cur->data));
2048         g_slist_free(item_list);
2049
2050         namespace = g_new(IMAPNameSpace, 1);
2051         namespace->name = g_strdup("");
2052         namespace->separator = separator;
2053         folder->ns_personal = g_list_append(NULL, namespace);
2054 }
2055
2056 static IMAPNameSpace *imap_find_namespace_from_list(GList *ns_list,
2057                                                     const gchar *path)
2058 {
2059         IMAPNameSpace *namespace = NULL;
2060         gchar *tmp_path, *name;
2061
2062         if (!path) path = "";
2063
2064         for (; ns_list != NULL; ns_list = ns_list->next) {
2065                 IMAPNameSpace *tmp_ns = ns_list->data;
2066
2067                 Xstrcat_a(tmp_path, path, "/", return namespace);
2068                 Xstrdup_a(name, tmp_ns->name, return namespace);
2069                 if (tmp_ns->separator && tmp_ns->separator != '/') {
2070                         subst_char(tmp_path, tmp_ns->separator, '/');
2071                         subst_char(name, tmp_ns->separator, '/');
2072                 }
2073                 if (strncmp(tmp_path, name, strlen(name)) == 0)
2074                         namespace = tmp_ns;
2075         }
2076
2077         return namespace;
2078 }
2079
2080 static IMAPNameSpace *imap_find_namespace(IMAPFolder *folder,
2081                                           const gchar *path)
2082 {
2083         IMAPNameSpace *namespace;
2084
2085         g_return_val_if_fail(folder != NULL, NULL);
2086
2087         namespace = imap_find_namespace_from_list(folder->ns_personal, path);
2088         if (namespace) return namespace;
2089         namespace = imap_find_namespace_from_list(folder->ns_others, path);
2090         if (namespace) return namespace;
2091         namespace = imap_find_namespace_from_list(folder->ns_shared, path);
2092         if (namespace) return namespace;
2093
2094         return NULL;
2095 }
2096
2097 static gchar imap_get_path_separator(IMAPFolder *folder, const gchar *path)
2098 {
2099         IMAPNameSpace *namespace;
2100         gchar separator = '/';
2101
2102         namespace = imap_find_namespace(folder, path);
2103         if (namespace && namespace->separator)
2104                 separator = namespace->separator;
2105
2106         return separator;
2107 }
2108
2109 static gchar *imap_get_real_path(IMAPFolder *folder, const gchar *path)
2110 {
2111         gchar *real_path;
2112         gchar separator;
2113
2114         g_return_val_if_fail(folder != NULL, NULL);
2115         g_return_val_if_fail(path != NULL, NULL);
2116
2117         real_path = imap_locale_to_modified_utf7(path);
2118         separator = imap_get_path_separator(folder, path);
2119         imap_path_separator_subst(real_path, separator);
2120
2121         return real_path;
2122 }
2123
2124 static gchar *imap_parse_atom(SockInfo *sock, gchar *src,
2125                               gchar *dest, gint dest_len, GString *str)
2126 {
2127         gchar *cur_pos = src;
2128         gchar *nextline;
2129
2130         g_return_val_if_fail(str != NULL, cur_pos);
2131
2132         /* read the next line if the current response buffer is empty */
2133         while (isspace(*cur_pos)) cur_pos++;
2134         while (*cur_pos == '\0') {
2135                 if ((nextline = sock_getline(sock)) == NULL)
2136                         return cur_pos;
2137                 g_string_assign(str, nextline);
2138                 cur_pos = str->str;
2139                 strretchomp(nextline);
2140                 /* log_print("IMAP4< %s\n", nextline); */
2141                 debug_print("IMAP4< %s\n", nextline);
2142                 g_free(nextline);
2143
2144                 while (isspace(*cur_pos)) cur_pos++;
2145         }
2146
2147         if (!strncmp(cur_pos, "NIL", 3)) {
2148                 *dest = '\0';
2149                 cur_pos += 3;
2150         } else if (*cur_pos == '\"') {
2151                 gchar *p;
2152
2153                 p = get_quoted(cur_pos, '\"', dest, dest_len);
2154                 cur_pos = p ? p : cur_pos + 2;
2155         } else if (*cur_pos == '{') {
2156                 gchar buf[32];
2157                 gint len;
2158                 gint line_len = 0;
2159
2160                 cur_pos = strchr_cpy(cur_pos + 1, '}', buf, sizeof(buf));
2161                 len = atoi(buf);
2162                 g_return_val_if_fail(len >= 0, cur_pos);
2163
2164                 g_string_truncate(str, 0);
2165                 cur_pos = str->str;
2166
2167                 do {
2168                         if ((nextline = sock_getline(sock)) == NULL)
2169                                 return cur_pos;
2170                         line_len += strlen(nextline);
2171                         g_string_append(str, nextline);
2172                         cur_pos = str->str;
2173                         strretchomp(nextline);
2174                         /* log_print("IMAP4< %s\n", nextline); */
2175                         debug_print("IMAP4< %s\n", nextline);
2176                         g_free(nextline);
2177                 } while (line_len < len);
2178
2179                 memcpy(dest, cur_pos, MIN(len, dest_len - 1));
2180                 dest[MIN(len, dest_len - 1)] = '\0';
2181                 cur_pos += len;
2182         }
2183
2184         return cur_pos;
2185 }
2186
2187 static gchar *imap_get_header(SockInfo *sock, gchar *cur_pos, gchar **headers,
2188                               GString *str)
2189 {
2190         gchar *nextline;
2191         gchar buf[32];
2192         gint len;
2193         gint block_len = 0;
2194
2195         *headers = NULL;
2196
2197         g_return_val_if_fail(str != NULL, cur_pos);
2198
2199         while (isspace(*cur_pos)) cur_pos++;
2200
2201         g_return_val_if_fail(*cur_pos == '{', cur_pos);
2202
2203         cur_pos = strchr_cpy(cur_pos + 1, '}', buf, sizeof(buf));
2204         len = atoi(buf);
2205         g_return_val_if_fail(len >= 0, cur_pos);
2206
2207         g_string_truncate(str, 0);
2208         cur_pos = str->str;
2209
2210         do {
2211                 if ((nextline = sock_getline(sock)) == NULL)
2212                         return cur_pos;
2213                 block_len += strlen(nextline);
2214                 g_string_append(str, nextline);
2215                 cur_pos = str->str;
2216                 strretchomp(nextline);
2217                 /* debug_print("IMAP4< %s\n", nextline); */
2218                 g_free(nextline);
2219         } while (block_len < len);
2220
2221         debug_print("IMAP4< [contents of BODY.PEEK[HEADER.FIELDS (...)]]\n");
2222
2223         *headers = g_strndup(cur_pos, len);
2224         cur_pos += len;
2225
2226         while (isspace(*cur_pos)) cur_pos++;
2227         while (*cur_pos == '\0') {
2228                 if ((nextline = sock_getline(sock)) == NULL)
2229                         return cur_pos;
2230                 g_string_assign(str, nextline);
2231                 cur_pos = str->str;
2232                 strretchomp(nextline);
2233                 debug_print("IMAP4< %s\n", nextline);
2234                 g_free(nextline);
2235
2236                 while (isspace(*cur_pos)) cur_pos++;
2237         }
2238
2239         return cur_pos;
2240 }
2241
2242 static MsgFlags imap_parse_flags(const gchar *flag_str)  
2243 {
2244         const gchar *p = flag_str;
2245         MsgFlags flags = {0, 0};
2246
2247         flags.perm_flags = MSG_UNREAD;
2248
2249         while ((p = strchr(p, '\\')) != NULL) {
2250                 p++;
2251
2252                 if (g_strncasecmp(p, "Recent", 6) == 0 && MSG_IS_UNREAD(flags)) {
2253                         MSG_SET_PERM_FLAGS(flags, MSG_NEW);
2254                 } else if (g_strncasecmp(p, "Seen", 4) == 0) {
2255                         MSG_UNSET_PERM_FLAGS(flags, MSG_NEW|MSG_UNREAD);
2256                 } else if (g_strncasecmp(p, "Deleted", 7) == 0) {
2257                         MSG_SET_PERM_FLAGS(flags, MSG_DELETED);
2258                 } else if (g_strncasecmp(p, "Flagged", 7) == 0) {
2259                         MSG_SET_PERM_FLAGS(flags, MSG_MARKED);
2260                 } else if (g_strncasecmp(p, "Answered", 8) == 0) {
2261                         MSG_SET_PERM_FLAGS(flags, MSG_REPLIED);
2262                 }
2263         }
2264
2265         return flags;
2266 }
2267
2268 static MsgInfo *imap_parse_envelope(SockInfo *sock, FolderItem *item,
2269                                     GString *line_str)
2270 {
2271         gchar buf[IMAPBUFSIZE];
2272         MsgInfo *msginfo = NULL;
2273         gchar *cur_pos;
2274         gint msgnum;
2275         guint32 uid = 0;
2276         size_t size = 0;
2277         MsgFlags flags = {0, 0}, imap_flags = {0, 0};
2278
2279         g_return_val_if_fail(line_str != NULL, NULL);
2280         g_return_val_if_fail(line_str->str[0] == '*' &&
2281                              line_str->str[1] == ' ', NULL);
2282
2283         MSG_SET_TMP_FLAGS(flags, MSG_IMAP);
2284         if (item->stype == F_QUEUE) {
2285                 MSG_SET_TMP_FLAGS(flags, MSG_QUEUED);
2286         } else if (item->stype == F_DRAFT) {
2287                 MSG_SET_TMP_FLAGS(flags, MSG_DRAFT);
2288         }
2289
2290         cur_pos = line_str->str + 2;
2291
2292 #define PARSE_ONE_ELEMENT(ch)                                   \
2293 {                                                               \
2294         cur_pos = strchr_cpy(cur_pos, ch, buf, sizeof(buf));    \
2295         if (cur_pos == NULL) {                                  \
2296                 g_warning("cur_pos == NULL\n");                 \
2297                 procmsg_msginfo_free(msginfo);                  \
2298                 return NULL;                                    \
2299         }                                                       \
2300 }
2301
2302         PARSE_ONE_ELEMENT(' ');
2303         msgnum = atoi(buf);
2304
2305         PARSE_ONE_ELEMENT(' ');
2306         g_return_val_if_fail(!strcmp(buf, "FETCH"), NULL);
2307
2308         g_return_val_if_fail(*cur_pos == '(', NULL);
2309         cur_pos++;
2310
2311         while (*cur_pos != '\0' && *cur_pos != ')') {
2312                 while (*cur_pos == ' ') cur_pos++;
2313
2314                 if (!strncmp(cur_pos, "UID ", 4)) {
2315                         cur_pos += 4;
2316                         uid = strtoul(cur_pos, &cur_pos, 10);
2317                 } else if (!strncmp(cur_pos, "FLAGS ", 6)) {
2318                         cur_pos += 6;
2319                         if (*cur_pos != '(') {
2320                                 g_warning("*cur_pos != '('\n");
2321                                 procmsg_msginfo_free(msginfo);
2322                                 return NULL;
2323                         }
2324                         cur_pos++;
2325                         PARSE_ONE_ELEMENT(')');
2326                         imap_flags = imap_parse_flags(buf);
2327                 } else if (!strncmp(cur_pos, "RFC822.SIZE ", 12)) {
2328                         cur_pos += 12;
2329                         size = strtol(cur_pos, &cur_pos, 10);
2330                 } else if (!strncmp(cur_pos, "BODY[HEADER.FIELDS ", 19)) {
2331                         gchar *headers;
2332
2333                         cur_pos += 19;
2334                         if (*cur_pos != '(') {
2335                                 g_warning("*cur_pos != '('\n");
2336                                 procmsg_msginfo_free(msginfo);
2337                                 return NULL;
2338                         }
2339                         cur_pos++;
2340                         PARSE_ONE_ELEMENT(')');
2341                         if (*cur_pos != ']') {
2342                                 g_warning("*cur_pos != ']'\n");
2343                                 procmsg_msginfo_free(msginfo);
2344                                 return NULL;
2345                         }
2346                         cur_pos++;
2347
2348                         cur_pos = imap_get_header(sock, cur_pos, &headers,
2349                                                   line_str);
2350                         msginfo = procheader_parse_str(headers, flags, FALSE, FALSE);
2351                         g_free(headers);
2352                 } else {
2353                         g_warning("invalid FETCH response: %s\n", cur_pos);
2354                         break;
2355                 }
2356         }
2357
2358         if (msginfo) {
2359                 msginfo->msgnum = uid;
2360                 msginfo->size = size;
2361                 msginfo->flags.tmp_flags |= imap_flags.tmp_flags;
2362                 msginfo->flags.perm_flags = imap_flags.perm_flags;
2363         }
2364
2365         return msginfo;
2366 }
2367
2368 static gchar *imap_get_flag_str(IMAPFlags flags)
2369 {
2370         GString *str;
2371         gchar *ret;
2372
2373         str = g_string_new(NULL);
2374
2375         if (IMAP_IS_SEEN(flags))        g_string_append(str, "\\Seen ");
2376         if (IMAP_IS_ANSWERED(flags))    g_string_append(str, "\\Answered ");
2377         if (IMAP_IS_FLAGGED(flags))     g_string_append(str, "\\Flagged ");
2378         if (IMAP_IS_DELETED(flags))     g_string_append(str, "\\Deleted ");
2379         if (IMAP_IS_DRAFT(flags))       g_string_append(str, "\\Draft");
2380
2381         if (str->len > 0 && str->str[str->len - 1] == ' ')
2382                 g_string_truncate(str, str->len - 1);
2383
2384         ret = str->str;
2385         g_string_free(str, FALSE);
2386
2387         return ret;
2388 }
2389
2390 static gint imap_set_message_flags(IMAPSession *session,
2391                                    MsgNumberList *numlist,
2392                                    IMAPFlags flags,
2393                                    gboolean is_set)
2394 {
2395         gchar *cmd;
2396         gchar *flag_str;
2397         gint ok;
2398         GSList *seq_list, *cur;
2399         IMAPSet imapset;
2400
2401         flag_str = imap_get_flag_str(flags);
2402         cmd = g_strconcat(is_set ? "+FLAGS.SILENT (" : "-FLAGS.SILENT (",
2403                           flag_str, ")", NULL);
2404         g_free(flag_str);
2405
2406         seq_list = imap_get_seq_set_from_numlist(numlist);
2407         for (cur = seq_list; cur != NULL; cur = g_slist_next(cur)) {
2408                 imapset = cur->data;
2409
2410                 ok = imap_cmd_store(session, imapset, cmd);
2411         }
2412         imap_seq_set_free(seq_list);
2413         g_free(cmd);
2414
2415         return ok;
2416 }
2417
2418 static gint imap_select(IMAPSession *session, IMAPFolder *folder,
2419                         const gchar *path,
2420                         gint *exists, gint *recent, gint *unseen,
2421                         guint32 *uid_validity)
2422 {
2423         gchar *real_path;
2424         gint ok;
2425         gint exists_, recent_, unseen_, uid_validity_;
2426
2427         if (!exists || !recent || !unseen || !uid_validity) {
2428                 if (session->mbox && strcmp(session->mbox, path) == 0)
2429                         return IMAP_SUCCESS;
2430                 exists = &exists_;
2431                 recent = &recent_;
2432                 unseen = &unseen_;
2433                 uid_validity = &uid_validity_;
2434         }
2435
2436         g_free(session->mbox);
2437         session->mbox = NULL;
2438
2439         real_path = imap_get_real_path(folder, path);
2440         ok = imap_cmd_select(session, real_path,
2441                              exists, recent, unseen, uid_validity);
2442         if (ok != IMAP_SUCCESS)
2443                 log_warning(_("can't select folder: %s\n"), real_path);
2444         else {
2445                 session->mbox = g_strdup(path);
2446                 session->folder_content_changed = FALSE;
2447         }
2448         g_free(real_path);
2449
2450         return ok;
2451 }
2452
2453 #define THROW(err) { ok = err; goto catch; }
2454
2455 static gint imap_status(IMAPSession *session, IMAPFolder *folder,
2456                         const gchar *path,
2457                         gint *messages, gint *recent,
2458                         guint32 *uid_next, guint32 *uid_validity,
2459                         gint *unseen)
2460 {
2461         gchar *real_path;
2462         gchar *real_path_;
2463         gint ok;
2464         GPtrArray *argbuf = NULL;
2465         gchar *str;
2466
2467         if (messages && recent && uid_next && uid_validity && unseen) {
2468                 *messages = *recent = *uid_next = *uid_validity = *unseen = 0;
2469                 argbuf = g_ptr_array_new();
2470         }
2471
2472         real_path = imap_get_real_path(folder, path);
2473         QUOTE_IF_REQUIRED(real_path_, real_path);
2474         imap_gen_send(session, "STATUS %s "
2475                       "(MESSAGES RECENT UIDNEXT UIDVALIDITY UNSEEN)",
2476                       real_path_);
2477
2478         ok = imap_cmd_ok(session, argbuf);
2479         if (ok != IMAP_SUCCESS || !argbuf) THROW(ok);
2480
2481         str = search_array_str(argbuf, "STATUS");
2482         if (!str) THROW(IMAP_ERROR);
2483
2484         str = strchr(str, '(');
2485         if (!str) THROW(IMAP_ERROR);
2486         str++;
2487         while (*str != '\0' && *str != ')') {
2488                 while (*str == ' ') str++;
2489
2490                 if (!strncmp(str, "MESSAGES ", 9)) {
2491                         str += 9;
2492                         *messages = strtol(str, &str, 10);
2493                 } else if (!strncmp(str, "RECENT ", 7)) {
2494                         str += 7;
2495                         *recent = strtol(str, &str, 10);
2496                 } else if (!strncmp(str, "UIDNEXT ", 8)) {
2497                         str += 8;
2498                         *uid_next = strtoul(str, &str, 10);
2499                 } else if (!strncmp(str, "UIDVALIDITY ", 12)) {
2500                         str += 12;
2501                         *uid_validity = strtoul(str, &str, 10);
2502                 } else if (!strncmp(str, "UNSEEN ", 7)) {
2503                         str += 7;
2504                         *unseen = strtol(str, &str, 10);
2505                 } else {
2506                         g_warning("invalid STATUS response: %s\n", str);
2507                         break;
2508                 }
2509         }
2510
2511 catch:
2512         g_free(real_path);
2513         if (argbuf) {
2514                 ptr_array_free_strings(argbuf);
2515                 g_ptr_array_free(argbuf, TRUE);
2516         }
2517
2518         return ok;
2519 }
2520
2521 #undef THROW
2522
2523 static gboolean imap_has_capability(IMAPSession *session, const gchar *cap)
2524 {
2525         gchar **p;
2526         
2527         for (p = session->capability; *p != NULL; ++p) {
2528                 if (!g_strcasecmp(*p, cap))
2529                         return TRUE;
2530         }
2531
2532         return FALSE;
2533 }
2534
2535 void imap_free_capabilities(IMAPSession *session)
2536 {
2537         g_strfreev(session->capability);
2538         session->capability = NULL;
2539 }
2540
2541 /* low-level IMAP4rev1 commands */
2542
2543 static gint imap_cmd_authenticate(IMAPSession *session, const gchar *user,
2544                                   const gchar *pass, IMAPAuthType type)
2545 {
2546         gchar *auth_type;
2547         gint ok;
2548         gchar *buf = NULL;
2549         gchar *challenge;
2550         gint challenge_len;
2551         gchar hexdigest[33];
2552         gchar *response;
2553         gchar *response64;
2554
2555         auth_type = "CRAM-MD5";
2556
2557         imap_gen_send(session, "AUTHENTICATE %s", auth_type);
2558         ok = imap_gen_recv(session, &buf);
2559         if (ok != IMAP_SUCCESS || buf[0] != '+' || buf[1] != ' ') {
2560                 g_free(buf);
2561                 return IMAP_ERROR;
2562         }
2563
2564         challenge = g_malloc(strlen(buf + 2) + 1);
2565         challenge_len = base64_decode(challenge, buf + 2, -1);
2566         challenge[challenge_len] = '\0';
2567         g_free(buf);
2568         log_print("IMAP< [Decoded: %s]\n", challenge);
2569
2570         md5_hex_hmac(hexdigest, challenge, challenge_len, pass, strlen(pass));
2571         g_free(challenge);
2572
2573         response = g_strdup_printf("%s %s", user, hexdigest);
2574         log_print("IMAP> [Encoded: %s]\n", response);
2575         response64 = g_malloc((strlen(response) + 3) * 2 + 1);
2576         base64_encode(response64, response, strlen(response));
2577         g_free(response);
2578
2579         log_print("IMAP> %s\n", response64);
2580         sock_puts(SESSION(session)->sock, response64);
2581         ok = imap_cmd_ok(session, NULL);
2582         if (ok != IMAP_SUCCESS)
2583                 log_warning(_("IMAP4 authentication failed.\n"));
2584
2585         return ok;
2586 }
2587
2588 static gint imap_cmd_login(IMAPSession *session,
2589                            const gchar *user, const gchar *pass)
2590 {
2591         gchar *user_, *pass_;
2592         gint ok;
2593
2594         QUOTE_IF_REQUIRED(user_, user);
2595         QUOTE_IF_REQUIRED(pass_, pass);
2596         imap_gen_send(session, "LOGIN %s %s", user_, pass_);
2597
2598         ok = imap_cmd_ok(session, NULL);
2599         if (ok != IMAP_SUCCESS)
2600                 log_warning(_("IMAP4 login failed.\n"));
2601
2602         return ok;
2603 }
2604
2605 static gint imap_cmd_logout(IMAPSession *session)
2606 {
2607         imap_gen_send(session, "LOGOUT");
2608         return imap_cmd_ok(session, NULL);
2609 }
2610
2611 static gint imap_cmd_noop(IMAPSession *session)
2612 {
2613         imap_gen_send(session, "NOOP");
2614         return imap_cmd_ok(session, NULL);
2615 }
2616
2617 static gint imap_cmd_starttls(IMAPSession *session)
2618 {
2619         imap_gen_send(session, "STARTTLS");
2620         return imap_cmd_ok(session, NULL);
2621 }
2622
2623 #define THROW(err) { ok = err; goto catch; }
2624
2625 static gint imap_cmd_namespace(IMAPSession *session, gchar **ns_str)
2626 {
2627         gint ok;
2628         GPtrArray *argbuf;
2629         gchar *str;
2630
2631         argbuf = g_ptr_array_new();
2632
2633         imap_gen_send(session, "NAMESPACE");
2634         if ((ok = imap_cmd_ok(session, argbuf)) != IMAP_SUCCESS) THROW(ok);
2635
2636         str = search_array_str(argbuf, "NAMESPACE");
2637         if (!str) THROW(IMAP_ERROR);
2638
2639         *ns_str = g_strdup(str);
2640
2641 catch:
2642         ptr_array_free_strings(argbuf);
2643         g_ptr_array_free(argbuf, TRUE);
2644
2645         return ok;
2646 }
2647
2648 #undef THROW
2649
2650 static gint imap_cmd_list(IMAPSession *session, const gchar *ref,
2651                           const gchar *mailbox, GPtrArray *argbuf)
2652 {
2653         gchar *ref_, *mailbox_;
2654
2655         if (!ref) ref = "\"\"";
2656         if (!mailbox) mailbox = "\"\"";
2657
2658         QUOTE_IF_REQUIRED(ref_, ref);
2659         QUOTE_IF_REQUIRED(mailbox_, mailbox);
2660         imap_gen_send(session, "LIST %s %s", ref_, mailbox_);
2661
2662         return imap_cmd_ok(session, argbuf);
2663 }
2664
2665 #define THROW goto catch
2666
2667 static gint imap_cmd_do_select(IMAPSession *session, const gchar *folder,
2668                                gboolean examine,
2669                                gint *exists, gint *recent, gint *unseen,
2670                                guint32 *uid_validity)
2671 {
2672         gint ok;
2673         gchar *resp_str;
2674         GPtrArray *argbuf;
2675         gchar *select_cmd;
2676         gchar *folder_;
2677
2678         *exists = *recent = *unseen = *uid_validity = 0;
2679         argbuf = g_ptr_array_new();
2680
2681         if (examine)
2682                 select_cmd = "EXAMINE";
2683         else
2684                 select_cmd = "SELECT";
2685
2686         QUOTE_IF_REQUIRED(folder_, folder);
2687         imap_gen_send(session, "%s %s", select_cmd, folder_);
2688
2689         if ((ok = imap_cmd_ok(session, argbuf)) != IMAP_SUCCESS) THROW;
2690
2691         resp_str = search_array_contain_str(argbuf, "EXISTS");
2692         if (resp_str) {
2693                 if (sscanf(resp_str,"%d EXISTS", exists) != 1) {
2694                         g_warning("imap_cmd_select(): invalid EXISTS line.\n");
2695                         THROW;
2696                 }
2697         }
2698
2699         resp_str = search_array_contain_str(argbuf, "RECENT");
2700         if (resp_str) {
2701                 if (sscanf(resp_str, "%d RECENT", recent) != 1) {
2702                         g_warning("imap_cmd_select(): invalid RECENT line.\n");
2703                         THROW;
2704                 }
2705         }
2706
2707         resp_str = search_array_contain_str(argbuf, "UIDVALIDITY");
2708         if (resp_str) {
2709                 if (sscanf(resp_str, "OK [UIDVALIDITY %u] ", uid_validity)
2710                     != 1) {
2711                         g_warning("imap_cmd_select(): invalid UIDVALIDITY line.\n");
2712                         THROW;
2713                 }
2714         }
2715
2716         resp_str = search_array_contain_str(argbuf, "UNSEEN");
2717         if (resp_str) {
2718                 if (sscanf(resp_str, "OK [UNSEEN %d] ", unseen) != 1) {
2719                         g_warning("imap_cmd_select(): invalid UNSEEN line.\n");
2720                         THROW;
2721                 }
2722         }
2723
2724 catch:
2725         ptr_array_free_strings(argbuf);
2726         g_ptr_array_free(argbuf, TRUE);
2727
2728         return ok;
2729 }
2730
2731 static gint imap_cmd_select(IMAPSession *session, const gchar *folder,
2732                             gint *exists, gint *recent, gint *unseen,
2733                             guint32 *uid_validity)
2734 {
2735         return imap_cmd_do_select(session, folder, FALSE,
2736                                   exists, recent, unseen, uid_validity);
2737 }
2738
2739 static gint imap_cmd_examine(IMAPSession *session, const gchar *folder,
2740                              gint *exists, gint *recent, gint *unseen,
2741                              guint32 *uid_validity)
2742 {
2743         return imap_cmd_do_select(session, folder, TRUE,
2744                                   exists, recent, unseen, uid_validity);
2745 }
2746
2747 #undef THROW
2748
2749 static gint imap_cmd_create(IMAPSession *session, const gchar *folder)
2750 {
2751         gchar *folder_;
2752
2753         QUOTE_IF_REQUIRED(folder_, folder);
2754         imap_gen_send(session, "CREATE %s", folder_);
2755
2756         return imap_cmd_ok(session, NULL);
2757 }
2758
2759 static gint imap_cmd_rename(IMAPSession *session, const gchar *old_folder,
2760                             const gchar *new_folder)
2761 {
2762         gchar *old_folder_, *new_folder_;
2763
2764         QUOTE_IF_REQUIRED(old_folder_, old_folder);
2765         QUOTE_IF_REQUIRED(new_folder_, new_folder);
2766         imap_gen_send(session, "RENAME %s %s", old_folder_, new_folder_);
2767
2768         return imap_cmd_ok(session, NULL);
2769 }
2770
2771 static gint imap_cmd_delete(IMAPSession *session, const gchar *folder)
2772 {
2773         gchar *folder_;
2774
2775         QUOTE_IF_REQUIRED(folder_, folder);
2776         imap_gen_send(session, "DELETE %s", folder_);
2777
2778         return imap_cmd_ok(session, NULL);
2779 }
2780
2781 static gint imap_cmd_search(IMAPSession *session, const gchar *criteria, GSList **list)
2782 {
2783         gint ok;
2784         gchar *uidlist;
2785         GPtrArray *argbuf;
2786
2787         g_return_val_if_fail(criteria != NULL, IMAP_ERROR);
2788         g_return_val_if_fail(list != NULL, IMAP_ERROR);
2789
2790         *list = NULL;
2791         
2792         argbuf = g_ptr_array_new();
2793         imap_gen_send(session, "UID SEARCH %s", criteria);
2794
2795         ok = imap_cmd_ok(session, argbuf);
2796         if (ok != IMAP_SUCCESS) {
2797                 ptr_array_free_strings(argbuf);
2798                 g_ptr_array_free(argbuf, TRUE);
2799                 return ok;
2800         }
2801
2802         if ((uidlist = search_array_str(argbuf, "SEARCH ")) != NULL) {
2803                 gchar **strlist, **p;
2804
2805                 strlist = g_strsplit(uidlist + 7, " ", 0);
2806                 for (p = strlist; *p != NULL; ++p) {
2807                         guint msgnum;
2808
2809                         if (sscanf(*p, "%d", &msgnum) == 1)
2810                                 *list = g_slist_append(*list, GINT_TO_POINTER(msgnum));
2811                 }
2812                 g_strfreev(strlist);
2813         }
2814         ptr_array_free_strings(argbuf);
2815         g_ptr_array_free(argbuf, TRUE);
2816
2817         return IMAP_SUCCESS;
2818 }
2819
2820 static gint imap_cmd_fetch(IMAPSession *session, guint32 uid, const gchar *filename)
2821 {
2822         gint ok;
2823         gchar *buf = NULL;
2824         gchar *cur_pos;
2825         gchar size_str[32];
2826         glong size_num;
2827
2828         g_return_val_if_fail(filename != NULL, IMAP_ERROR);
2829
2830         imap_gen_send(session, "UID FETCH %d BODY.PEEK[]", uid);
2831
2832         while ((ok = imap_gen_recv(session, &buf)) == IMAP_SUCCESS) {
2833                 if (buf[0] != '*' || buf[1] != ' ') {
2834                         g_free(buf);
2835                         return IMAP_ERROR;
2836                 }
2837                 if (strstr(buf, "FETCH") != NULL) break;
2838                 g_free(buf);
2839         }
2840         if (ok != IMAP_SUCCESS) {
2841                 g_free(buf);
2842                 return ok;
2843         }
2844
2845 #define RETURN_ERROR_IF_FAIL(cond)      \
2846         if (!(cond)) {                  \
2847                 g_free(buf);            \
2848                 return IMAP_ERROR;      \
2849         }
2850
2851         cur_pos = strchr(buf, '{');
2852         RETURN_ERROR_IF_FAIL(cur_pos != NULL);
2853         cur_pos = strchr_cpy(cur_pos + 1, '}', size_str, sizeof(size_str));
2854         RETURN_ERROR_IF_FAIL(cur_pos != NULL);
2855         size_num = atol(size_str);
2856         RETURN_ERROR_IF_FAIL(size_num >= 0);
2857
2858         RETURN_ERROR_IF_FAIL(*cur_pos == '\0');
2859
2860 #undef RETURN_ERROR_IF_FAIL
2861
2862         g_free(buf);
2863
2864         if (recv_bytes_write_to_file(SESSION(session)->sock, size_num, filename) != 0)
2865                 return IMAP_ERROR;
2866
2867         if (imap_gen_recv(session, &buf) != IMAP_SUCCESS) {
2868                 g_free(buf);
2869                 return IMAP_ERROR;
2870         }
2871
2872         if (buf[0] == '\0' || buf[strlen(buf) - 1] != ')') {
2873                 g_free(buf);
2874                 return IMAP_ERROR;
2875         }
2876         g_free(buf);
2877
2878         ok = imap_cmd_ok(session, NULL);
2879
2880         return ok;
2881 }
2882
2883 static gint imap_cmd_append(IMAPSession *session, const gchar *destfolder,
2884                             const gchar *file, IMAPFlags flags, guint32 *new_uid)
2885 {
2886         gint ok;
2887         gint size;
2888         gchar *destfolder_;
2889         gchar *flag_str;
2890         guint32 new_uid_;
2891         gchar *ret = NULL;
2892         gchar buf[BUFFSIZE];
2893         FILE *fp;
2894         GPtrArray *argbuf;
2895         gchar *resp_str;
2896
2897         g_return_val_if_fail(file != NULL, IMAP_ERROR);
2898
2899         size = get_file_size_as_crlf(file);
2900         if ((fp = fopen(file, "rb")) == NULL) {
2901                 FILE_OP_ERROR(file, "fopen");
2902                 return -1;
2903         }
2904         QUOTE_IF_REQUIRED(destfolder_, destfolder);
2905         flag_str = imap_get_flag_str(flags);
2906         imap_gen_send(session, "APPEND %s (%s) {%d}", 
2907                       destfolder_, flag_str, size);
2908         g_free(flag_str);
2909
2910         ok = imap_gen_recv(session, &ret);
2911         if (ok != IMAP_SUCCESS || ret[0] != '+' || ret[1] != ' ') {
2912                 log_warning(_("can't append %s to %s\n"), file, destfolder_);
2913                 g_free(ret);
2914                 fclose(fp);
2915                 return IMAP_ERROR;
2916         }
2917         g_free(ret);
2918
2919         log_print("IMAP4> %s\n", _("(sending file...)"));
2920
2921         while (fgets(buf, sizeof(buf), fp) != NULL) {
2922                 strretchomp(buf);
2923                 if (sock_puts(SESSION(session)->sock, buf) < 0) {
2924                         fclose(fp);
2925                         return -1;
2926                 }
2927         }
2928
2929         if (ferror(fp)) {
2930                 FILE_OP_ERROR(file, "fgets");
2931                 fclose(fp);
2932                 return -1;
2933         }
2934
2935         sock_puts(SESSION(session)->sock, "");
2936
2937         fclose(fp);
2938
2939         if (new_uid != NULL)
2940                 *new_uid = 0;
2941
2942         if (new_uid != NULL && session->uidplus) {
2943                 argbuf = g_ptr_array_new();
2944
2945                 ok = imap_cmd_ok(session, argbuf);
2946                 if ((ok == IMAP_SUCCESS) && (argbuf->len > 0)) {
2947                         resp_str = g_ptr_array_index(argbuf, argbuf->len - 1);
2948                         if (resp_str &&
2949                             sscanf(resp_str, "%*u OK [APPENDUID %*u %u]",
2950                                    &new_uid_) == 1) {
2951                                 *new_uid = new_uid_;
2952                         }
2953                 }
2954
2955                 ptr_array_free_strings(argbuf);
2956                 g_ptr_array_free(argbuf, TRUE);
2957         } else
2958                 ok = imap_cmd_ok(session, NULL);
2959
2960         if (ok != IMAP_SUCCESS)
2961                 log_warning(_("can't append message to %s\n"),
2962                             destfolder_);
2963
2964         return ok;
2965 }
2966
2967 static MsgNumberList *imapset_to_numlist(IMAPSet imapset)
2968 {
2969         gchar **ranges, **range;
2970         guint32 low, high;
2971         MsgNumberList *uids = NULL;
2972         
2973         ranges = g_strsplit(imapset, ",", 0);
2974         for (range = ranges; *range != NULL; range++) {
2975                 printf("%s\n", *range);
2976                 if(sscanf(*range, "%u:%u", &low, &high) == 1)
2977                         uids = g_slist_prepend(uids, GINT_TO_POINTER(low));
2978                 else {
2979                         int i;
2980                         for (i = low; i <= high; i++)
2981                                 uids = g_slist_prepend(uids, GINT_TO_POINTER(i));
2982                 }
2983         }
2984         uids = g_slist_reverse(uids);
2985         g_strfreev(ranges);
2986
2987         return uids;
2988 }
2989
2990 static gint imap_cmd_copy(IMAPSession *session, const gchar *seq_set,
2991                           const gchar *destfolder, GRelation *uid_mapping)
2992 {
2993         gint ok;
2994         gchar *destfolder_;
2995         
2996         g_return_val_if_fail(session != NULL, IMAP_ERROR);
2997         g_return_val_if_fail(seq_set != NULL, IMAP_ERROR);
2998         g_return_val_if_fail(destfolder != NULL, IMAP_ERROR);
2999
3000         QUOTE_IF_REQUIRED(destfolder_, destfolder);
3001         imap_gen_send(session, "UID COPY %s %s", seq_set, destfolder_);
3002
3003         if (uid_mapping != NULL && session->uidplus) {
3004                 GPtrArray *reply;               
3005                 gchar *resp_str = NULL, *olduids_str, *newuids_str;
3006                 MsgNumberList *olduids, *old_cur, *newuids, *new_cur;
3007
3008                 reply = g_ptr_array_new();
3009                 ok = imap_cmd_ok(session, reply);
3010                 if ((ok == IMAP_SUCCESS) && (reply->len > 0)) {
3011                         resp_str = g_ptr_array_index(reply, reply->len - 1);
3012                         if (resp_str) {
3013                                 olduids_str = g_new0(gchar, strlen(resp_str));
3014                                 newuids_str = g_new0(gchar, strlen(resp_str));
3015                                 if (sscanf(resp_str, "%*s OK [COPYUID %*u %[0-9,:] %[0-9,:]]",
3016                                            olduids_str, newuids_str) == 2) {
3017                                         olduids = imapset_to_numlist(olduids_str);
3018                                         newuids = imapset_to_numlist(newuids_str);
3019
3020                                         old_cur = olduids;
3021                                         new_cur = newuids;
3022                                         while(old_cur != NULL && new_cur != NULL) {
3023                                                 g_relation_insert(uid_mapping, 
3024                                                                   GPOINTER_TO_INT(old_cur->data),
3025                                                                   GPOINTER_TO_INT(new_cur->data));
3026                                                 old_cur = g_slist_next(old_cur);
3027                                                 new_cur = g_slist_next(new_cur);
3028                                         }
3029
3030                                         g_slist_free(olduids);
3031                                         g_slist_free(newuids);
3032                                 }
3033                                 g_free(olduids_str);
3034                                 g_free(newuids_str);
3035                         }
3036                 }
3037                 ptr_array_free_strings(reply);
3038                 g_ptr_array_free(reply, TRUE);
3039         } else
3040                 ok = imap_cmd_ok(session, NULL);
3041
3042         if (ok != IMAP_SUCCESS)
3043                 log_warning(_("can't copy %s to %s\n"), seq_set, destfolder_);
3044
3045         return ok;
3046 }
3047
3048 gint imap_cmd_envelope(IMAPSession *session, IMAPSet set)
3049 {
3050         static GString *header_fields = NULL;
3051
3052         if (header_fields == NULL) {
3053                 const HeaderEntry *headers, *elem;
3054
3055                 headers = procheader_get_headernames(FALSE);
3056                 header_fields = g_string_new("");
3057
3058                 for (elem = headers; elem->name != NULL; ++elem) {
3059                         gint namelen = strlen(elem->name);
3060
3061                         /* Header fields ending with space are not rfc822 headers */
3062                         if (elem->name[namelen - 1] == ' ')
3063                                 continue;
3064
3065                         /* strip : at the of header field */
3066                         if(elem->name[namelen - 1] == ':')
3067                                 namelen--;
3068                         
3069                         if (namelen <= 0)
3070                                 continue;
3071
3072                         g_string_sprintfa(header_fields, "%s%.*s",
3073                                         header_fields->str[0] != '\0' ? " " : "",
3074                                         namelen, elem->name);
3075                 }
3076         }
3077
3078         imap_gen_send
3079                 (session, "UID FETCH %s (UID FLAGS RFC822.SIZE BODY.PEEK[HEADER.FIELDS (%s)])",
3080                  set, header_fields->str);
3081
3082         return IMAP_SUCCESS;
3083 }
3084
3085 static gint imap_cmd_store(IMAPSession *session, IMAPSet seq_set,
3086                            gchar *sub_cmd)
3087 {
3088         gint ok;
3089
3090         imap_gen_send(session, "UID STORE %s %s", seq_set, sub_cmd);
3091
3092         if ((ok = imap_cmd_ok(session, NULL)) != IMAP_SUCCESS) {
3093                 log_warning(_("error while imap command: STORE %s %s\n"),
3094                             seq_set, sub_cmd);
3095                 return ok;
3096         }
3097
3098         return IMAP_SUCCESS;
3099 }
3100
3101 static gint imap_cmd_expunge(IMAPSession *session, IMAPSet seq_set)
3102 {
3103         gint ok;
3104
3105         if (seq_set && session->uidplus)
3106                 imap_gen_send(session, "UID EXPUNGE %s", seq_set);
3107         else    
3108                 imap_gen_send(session, "EXPUNGE");
3109         if ((ok = imap_cmd_ok(session, NULL)) != IMAP_SUCCESS) {
3110                 log_warning(_("error while imap command: EXPUNGE\n"));
3111                 return ok;
3112         }
3113
3114         return IMAP_SUCCESS;
3115 }
3116
3117 static gint imap_cmd_close(IMAPSession *session)
3118 {
3119         gint ok;
3120
3121         imap_gen_send(session, "CLOSE");
3122         if ((ok = imap_cmd_ok(session, NULL)) != IMAP_SUCCESS)
3123                 log_warning(_("error while imap command: CLOSE\n"));
3124
3125         return ok;
3126 }
3127
3128 static gint imap_cmd_ok(IMAPSession *session, GPtrArray *argbuf)
3129 {
3130         gint ok = IMAP_SUCCESS;
3131         gchar *buf;
3132         gint cmd_num;
3133         gchar *data;
3134
3135         while ((ok = imap_gen_recv(session, &buf))
3136                == IMAP_SUCCESS) {
3137                 // make sure data is long enough for any substring of buf
3138                 data = alloca(strlen(buf) + 1);
3139
3140                 // untagged line read
3141                 if (buf[0] == '*' && buf[1] == ' ') {
3142                         gint num;
3143                         if (argbuf)
3144                                 g_ptr_array_add(argbuf, g_strdup(buf + 2));
3145
3146                         if (sscanf(buf + 2, "%d %s", &num, data) >= 2) {
3147                                 if (!strcmp(data, "EXISTS")) {
3148                                         session->exists = num;
3149                                         session->folder_content_changed = TRUE;
3150                                 }
3151
3152                                 if(!strcmp(data, "EXPUNGE")) {
3153                                         session->exists--;
3154                                         session->folder_content_changed = TRUE;
3155                                 }
3156                         }
3157                 // tagged line with correct tag and OK response found
3158                 } else if ((sscanf(buf, "%d %s", &cmd_num, data) >= 2) &&
3159                            (cmd_num == session->cmd_count) &&
3160                            !strcmp(data, "OK")) {
3161                         if (argbuf)
3162                                 g_ptr_array_add(argbuf, g_strdup(buf));
3163                         break;
3164                 // everything else
3165                 } else {
3166                         ok = IMAP_ERROR;
3167                         break;
3168                 }
3169                 g_free(buf);
3170         }
3171         g_free(buf);
3172
3173         return ok;
3174 }
3175
3176 static void imap_gen_send(IMAPSession *session, const gchar *format, ...)
3177 {
3178         gchar *buf;
3179         gchar *tmp;
3180         gchar *p;
3181         va_list args;
3182
3183         va_start(args, format);
3184         tmp = g_strdup_vprintf(format, args);
3185         va_end(args);
3186
3187         session->cmd_count++;
3188
3189         buf = g_strdup_printf("%d %s\r\n", session->cmd_count, tmp);
3190         if (!strncasecmp(tmp, "LOGIN ", 6) && (p = strchr(tmp + 6, ' '))) {
3191                 *p = '\0';
3192                 log_print("IMAP4> %d %s ********\n", session->cmd_count, tmp);
3193         } else
3194                 log_print("IMAP4> %d %s\n", session->cmd_count, tmp);
3195
3196         sock_write_all(SESSION(session)->sock, buf, strlen(buf));
3197         g_free(tmp);
3198         g_free(buf);
3199 }
3200
3201 static gint imap_gen_recv(IMAPSession *session, gchar **ret)
3202 {
3203         if ((*ret = sock_getline(SESSION(session)->sock)) == NULL)
3204                 return IMAP_SOCKET;
3205
3206         strretchomp(*ret);
3207
3208         log_print("IMAP4< %s\n", *ret);
3209
3210         return IMAP_SUCCESS;
3211 }
3212
3213
3214 /* misc utility functions */
3215
3216 static gchar *strchr_cpy(const gchar *src, gchar ch, gchar *dest, gint len)
3217 {
3218         gchar *tmp;
3219
3220         dest[0] = '\0';
3221         tmp = strchr(src, ch);
3222         if (!tmp)
3223                 return NULL;
3224
3225         memcpy(dest, src, MIN(tmp - src, len - 1));
3226         dest[MIN(tmp - src, len - 1)] = '\0';
3227
3228         return tmp + 1;
3229 }
3230
3231 static gchar *get_quoted(const gchar *src, gchar ch, gchar *dest, gint len)
3232 {
3233         const gchar *p = src;
3234         gint n = 0;
3235
3236         g_return_val_if_fail(*p == ch, NULL);
3237
3238         *dest = '\0';
3239         p++;
3240
3241         while (*p != '\0' && *p != ch) {
3242                 if (n < len - 1) {
3243                         if (*p == '\\' && *(p + 1) != '\0')
3244                                 p++;
3245                         *dest++ = *p++;
3246                 } else
3247                         p++;
3248                 n++;
3249         }
3250
3251         *dest = '\0';
3252         return (gchar *)(*p == ch ? p + 1 : p);
3253 }
3254
3255 static gchar *search_array_contain_str(GPtrArray *array, const gchar *str)
3256 {
3257         gint i;
3258
3259         for (i = 0; i < array->len; i++) {
3260                 gchar *tmp;
3261
3262                 tmp = g_ptr_array_index(array, i);
3263                 if (strstr(tmp, str) != NULL)
3264                         return tmp;
3265         }
3266
3267         return NULL;
3268 }
3269
3270 static gchar *search_array_str(GPtrArray *array, const gchar *str)
3271 {
3272         gint i;
3273         gint len;
3274
3275         len = strlen(str);
3276
3277         for (i = 0; i < array->len; i++) {
3278                 gchar *tmp;
3279
3280                 tmp = g_ptr_array_index(array, i);
3281                 if (!strncmp(tmp, str, len))
3282                         return tmp;
3283         }
3284
3285         return NULL;
3286 }
3287
3288 static void imap_path_separator_subst(gchar *str, gchar separator)
3289 {
3290         gchar *p;
3291         gboolean in_escape = FALSE;
3292
3293         if (!separator || separator == '/') return;
3294
3295         for (p = str; *p != '\0'; p++) {
3296                 if (*p == '/' && !in_escape)
3297                         *p = separator;
3298                 else if (*p == '&' && *(p + 1) != '-' && !in_escape)
3299                         in_escape = TRUE;
3300                 else if (*p == '-' && in_escape)
3301                         in_escape = FALSE;
3302         }
3303 }
3304
3305 static gchar *imap_modified_utf7_to_locale(const gchar *mutf7_str)
3306 {
3307 #if !HAVE_ICONV
3308         const gchar *from_p;
3309         gchar *to, *to_p;
3310
3311         to = g_malloc(strlen(mutf7_str) + 1);
3312         to_p = to;
3313
3314         for (from_p = mutf7_str; *from_p != '\0'; from_p++) {
3315                 if (*from_p == '&' && *(from_p + 1) == '-') {
3316                         *to_p++ = '&';
3317                         from_p++;
3318                 } else
3319                         *to_p++ = *from_p;
3320         }
3321         *to_p = '\0';
3322
3323         return to;
3324 #else
3325         static iconv_t cd = (iconv_t)-1;
3326         static gboolean iconv_ok = TRUE;
3327         GString *norm_utf7;
3328         gchar *norm_utf7_p;
3329         size_t norm_utf7_len;
3330         const gchar *p;
3331         gchar *to_str, *to_p;
3332         size_t to_len;
3333         gboolean in_escape = FALSE;
3334
3335         if (!iconv_ok) return g_strdup(mutf7_str);
3336
3337         if (cd == (iconv_t)-1) {
3338                 cd = iconv_open(conv_get_current_charset_str(), "UTF-7");
3339                 if (cd == (iconv_t)-1) {
3340                         g_warning("iconv cannot convert UTF-7 to %s\n",
3341                                   conv_get_current_charset_str());
3342                         iconv_ok = FALSE;
3343                         return g_strdup(mutf7_str);
3344                 }
3345         }
3346
3347         norm_utf7 = g_string_new(NULL);
3348
3349         for (p = mutf7_str; *p != '\0'; p++) {
3350                 /* replace: '&'  -> '+',
3351                             "&-" -> '&',
3352                             escaped ','  -> '/' */
3353                 if (!in_escape && *p == '&') {
3354                         if (*(p + 1) != '-') {
3355                                 g_string_append_c(norm_utf7, '+');
3356                                 in_escape = TRUE;
3357                         } else {
3358                                 g_string_append_c(norm_utf7, '&');
3359                                 p++;
3360                         }
3361                 } else if (in_escape && *p == ',') {
3362                         g_string_append_c(norm_utf7, '/');
3363                 } else if (in_escape && *p == '-') {
3364                         g_string_append_c(norm_utf7, '-');
3365                         in_escape = FALSE;
3366                 } else {
3367                         g_string_append_c(norm_utf7, *p);
3368                 }
3369         }
3370
3371         norm_utf7_p = norm_utf7->str;
3372         norm_utf7_len = norm_utf7->len;
3373         to_len = strlen(mutf7_str) * 5;
3374         to_p = to_str = g_malloc(to_len + 1);
3375
3376         if (iconv(cd, (ICONV_CONST gchar **)&norm_utf7_p, &norm_utf7_len,
3377                   &to_p, &to_len) == -1) {
3378                 g_warning(_("iconv cannot convert UTF-7 to %s\n"),
3379                           conv_get_current_charset_str());
3380                 g_string_free(norm_utf7, TRUE);
3381                 g_free(to_str);
3382                 return g_strdup(mutf7_str);
3383         }
3384
3385         /* second iconv() call for flushing */
3386         iconv(cd, NULL, NULL, &to_p, &to_len);
3387         g_string_free(norm_utf7, TRUE);
3388         *to_p = '\0';
3389
3390         return to_str;
3391 #endif /* !HAVE_ICONV */
3392 }
3393
3394 static gchar *imap_locale_to_modified_utf7(const gchar *from)
3395 {
3396 #if !HAVE_ICONV
3397         const gchar *from_p;
3398         gchar *to, *to_p;
3399
3400         to = g_malloc(strlen(from) * 2 + 1);
3401         to_p = to;
3402
3403         for (from_p = from; *from_p != '\0'; from_p++) {
3404                 if (*from_p == '&') {
3405                         *to_p++ = '&';
3406                         *to_p++ = '-';
3407                 } else
3408                         *to_p++ = *from_p;
3409         }
3410         *to_p = '\0';
3411
3412         return to;
3413 #else
3414         static iconv_t cd = (iconv_t)-1;
3415         static gboolean iconv_ok = TRUE;
3416         gchar *norm_utf7, *norm_utf7_p;
3417         size_t from_len, norm_utf7_len;
3418         GString *to_str;
3419         gchar *from_tmp, *to, *p;
3420         gboolean in_escape = FALSE;
3421
3422         if (!iconv_ok) return g_strdup(from);
3423
3424         if (cd == (iconv_t)-1) {
3425                 cd = iconv_open("UTF-7", conv_get_current_charset_str());
3426                 if (cd == (iconv_t)-1) {
3427                         g_warning("iconv cannot convert %s to UTF-7\n",
3428                                   conv_get_current_charset_str());
3429                         iconv_ok = FALSE;
3430                         return g_strdup(from);
3431                 }
3432         }
3433
3434         Xstrdup_a(from_tmp, from, return g_strdup(from));
3435         from_len = strlen(from);
3436         norm_utf7_len = from_len * 5;
3437         Xalloca(norm_utf7, norm_utf7_len + 1, return g_strdup(from));
3438         norm_utf7_p = norm_utf7;
3439
3440 #define IS_PRINT(ch) (isprint(ch) && isascii(ch))
3441
3442         while (from_len > 0) {
3443                 if (*from_tmp == '+') {
3444                         *norm_utf7_p++ = '+';
3445                         *norm_utf7_p++ = '-';
3446                         norm_utf7_len -= 2;
3447                         from_tmp++;
3448                         from_len--;
3449                 } else if (IS_PRINT(*from_tmp)) {
3450                         /* printable ascii char */
3451                         *norm_utf7_p = *from_tmp;
3452                         norm_utf7_p++;
3453                         norm_utf7_len--;
3454                         from_tmp++;
3455                         from_len--;
3456                 } else {
3457                         size_t mb_len = 0, conv_len = 0;
3458
3459                         /* unprintable char: convert to UTF-7 */
3460                         p = from_tmp;
3461                         while (!IS_PRINT(*p) && conv_len < from_len) {
3462                                 mb_len = mblen(p, MB_LEN_MAX);
3463                                 if (mb_len <= 0) {
3464                                         g_warning("wrong multibyte sequence\n");
3465                                         return g_strdup(from);
3466                                 }
3467                                 conv_len += mb_len;
3468                                 p += mb_len;
3469                         }
3470
3471                         from_len -= conv_len;
3472                         if (iconv(cd, (ICONV_CONST gchar **)&from_tmp,
3473                                   &conv_len,
3474                                   &norm_utf7_p, &norm_utf7_len) == -1) {
3475                                 g_warning("iconv cannot convert %s to UTF-7\n",
3476                                           conv_get_current_charset_str());
3477                                 return g_strdup(from);
3478                         }
3479
3480                         /* second iconv() call for flushing */
3481                         iconv(cd, NULL, NULL, &norm_utf7_p, &norm_utf7_len);
3482                 }
3483         }
3484
3485 #undef IS_PRINT
3486
3487         *norm_utf7_p = '\0';
3488         to_str = g_string_new(NULL);
3489         for (p = norm_utf7; p < norm_utf7_p; p++) {
3490                 /* replace: '&' -> "&-",
3491                             '+' -> '&',
3492                             "+-" -> '+',
3493                             BASE64 '/' -> ',' */
3494                 if (!in_escape && *p == '&') {
3495                         g_string_append(to_str, "&-");
3496                 } else if (!in_escape && *p == '+') {
3497                         if (*(p + 1) == '-') {
3498                                 g_string_append_c(to_str, '+');
3499                                 p++;
3500                         } else {
3501                                 g_string_append_c(to_str, '&');
3502                                 in_escape = TRUE;
3503                         }
3504                 } else if (in_escape && *p == '/') {
3505                         g_string_append_c(to_str, ',');
3506                 } else if (in_escape && *p == '-') {
3507                         g_string_append_c(to_str, '-');
3508                         in_escape = FALSE;
3509                 } else {
3510                         g_string_append_c(to_str, *p);
3511                 }
3512         }
3513
3514         if (in_escape) {
3515                 in_escape = FALSE;
3516                 g_string_append_c(to_str, '-');
3517         }
3518
3519         to = to_str->str;
3520         g_string_free(to_str, FALSE);
3521
3522         return to;
3523 #endif /* !HAVE_ICONV */
3524 }
3525
3526 static GSList *imap_get_seq_set_from_numlist(MsgNumberList *numlist)
3527 {
3528         GString *str;
3529         GSList *sorted_list, *cur;
3530         guint first, last, next;
3531         gchar *ret_str;
3532         GSList *ret_list = NULL;
3533
3534         if (numlist == NULL)
3535                 return NULL;
3536
3537         str = g_string_sized_new(256);
3538
3539         sorted_list = g_slist_copy(numlist);
3540         sorted_list = g_slist_sort(sorted_list, g_int_compare);
3541
3542         first = GPOINTER_TO_INT(sorted_list->data);
3543
3544         for (cur = sorted_list; cur != NULL; cur = g_slist_next(cur)) {
3545                 last = GPOINTER_TO_INT(cur->data);
3546                 if (cur->next)
3547                         next = GPOINTER_TO_INT(cur->next->data);
3548                 else
3549                         next = 0;
3550
3551                 if (last + 1 != next || next == 0) {
3552                         if (str->len > 0)
3553                                 g_string_append_c(str, ',');
3554                         if (first == last)
3555                                 g_string_sprintfa(str, "%u", first);
3556                         else
3557                                 g_string_sprintfa(str, "%u:%u", first, last);
3558
3559                         first = next;
3560
3561                         if (str->len > IMAP_CMD_LIMIT) {
3562                                 ret_str = g_strdup(str->str);
3563                                 ret_list = g_slist_append(ret_list, ret_str);
3564                                 g_string_truncate(str, 0);
3565                         }
3566                 }
3567         }
3568
3569         if (str->len > 0) {
3570                 ret_str = g_strdup(str->str);
3571                 ret_list = g_slist_append(ret_list, ret_str);
3572         }
3573
3574         g_slist_free(sorted_list);
3575         g_string_free(str, TRUE);
3576
3577         return ret_list;
3578 }
3579
3580 static GSList *imap_get_seq_set_from_msglist(MsgInfoList *msglist)
3581 {
3582         MsgNumberList *numlist = NULL;
3583         MsgInfoList *cur;
3584         GSList *seq_list;
3585
3586         for (cur = msglist; cur != NULL; cur = g_slist_next(cur)) {
3587                 MsgInfo *msginfo = (MsgInfo *) cur->data;
3588
3589                 numlist = g_slist_append(numlist, GINT_TO_POINTER(msginfo->msgnum));
3590         }
3591         seq_list = imap_get_seq_set_from_numlist(numlist);
3592         g_slist_free(numlist);
3593
3594         return seq_list;
3595 }
3596
3597 static void imap_seq_set_free(GSList *seq_list)
3598 {
3599         slist_free_strings(seq_list);
3600         g_slist_free(seq_list);
3601 }
3602
3603
3604 static gboolean imap_rename_folder_func(GNode *node, gpointer data)
3605 {
3606         FolderItem *item = node->data;
3607         gchar **paths = data;
3608         const gchar *oldpath = paths[0];
3609         const gchar *newpath = paths[1];
3610         gchar *base;
3611         gchar *new_itempath;
3612         gint oldpathlen;
3613
3614         oldpathlen = strlen(oldpath);
3615         if (strncmp(oldpath, item->path, oldpathlen) != 0) {
3616                 g_warning("path doesn't match: %s, %s\n", oldpath, item->path);
3617                 return TRUE;
3618         }
3619
3620         base = item->path + oldpathlen;
3621         while (*base == G_DIR_SEPARATOR) base++;
3622         if (*base == '\0')
3623                 new_itempath = g_strdup(newpath);
3624         else
3625                 new_itempath = g_strconcat(newpath, G_DIR_SEPARATOR_S, base,
3626                                            NULL);
3627         g_free(item->path);
3628         item->path = new_itempath;
3629
3630         return FALSE;
3631 }
3632
3633 static gint get_list_of_uids(Folder *folder, IMAPFolderItem *item, GSList **msgnum_list)
3634 {
3635         gint ok, nummsgs = 0, lastuid_old;
3636         IMAPSession *session;
3637         GSList *uidlist, *elem;
3638         gchar *cmd_buf;
3639
3640         session = imap_session_get(folder);
3641         g_return_val_if_fail(session != NULL, -1);
3642
3643         ok = imap_select(session, IMAP_FOLDER(folder), item->item.path,
3644                          NULL, NULL, NULL, NULL);
3645         if (ok != IMAP_SUCCESS)
3646                 return -1;
3647
3648         cmd_buf = g_strdup_printf("UID %d:*", item->lastuid + 1);
3649         ok = imap_cmd_search(session, cmd_buf, &uidlist);
3650         g_free(cmd_buf);
3651
3652         if (ok == IMAP_SOCKET) {
3653                 session_destroy((Session *)session);
3654                 ((RemoteFolder *)folder)->session = NULL;
3655                 return -1;
3656         }
3657
3658         if (ok != IMAP_SUCCESS) {
3659                 gint i;
3660                 GPtrArray *argbuf;
3661
3662                 argbuf = g_ptr_array_new();
3663
3664                 cmd_buf = g_strdup_printf("UID FETCH %d:* (UID)", item->lastuid + 1);
3665                 imap_gen_send(session, cmd_buf);
3666                 g_free(cmd_buf);
3667                 ok = imap_cmd_ok(session, argbuf);
3668                 if (ok != IMAP_SUCCESS) {
3669                         ptr_array_free_strings(argbuf);
3670                         g_ptr_array_free(argbuf, TRUE);
3671                         return -1;
3672                 }
3673         
3674                 for(i = 0; i < argbuf->len; i++) {
3675                         int ret, msgnum;
3676         
3677                         if((ret = sscanf(g_ptr_array_index(argbuf, i), 
3678                                     "%*d FETCH (UID %d)", &msgnum)) == 1)
3679                                 uidlist = g_slist_prepend(uidlist, GINT_TO_POINTER(msgnum));
3680                 }
3681                 ptr_array_free_strings(argbuf);
3682                 g_ptr_array_free(argbuf, TRUE);
3683         }
3684
3685         lastuid_old = item->lastuid;
3686         *msgnum_list = g_slist_copy(item->uid_list);
3687         nummsgs = g_slist_length(*msgnum_list);
3688         debug_print("Got %d uids from cache\n", g_slist_length(item->uid_list));
3689
3690         for (elem = uidlist; elem != NULL; elem = g_slist_next(elem)) {
3691                 guint msgnum;
3692
3693                 msgnum = GPOINTER_TO_INT(elem->data);
3694                 if (msgnum > lastuid_old) {
3695                         *msgnum_list = g_slist_prepend(*msgnum_list, GINT_TO_POINTER(msgnum));
3696                         item->uid_list = g_slist_prepend(item->uid_list, GINT_TO_POINTER(msgnum));
3697                         nummsgs++;
3698
3699                         if(msgnum > item->lastuid)
3700                                 item->lastuid = msgnum;
3701                 }
3702         }
3703         g_slist_free(uidlist);
3704
3705         return nummsgs;
3706 }
3707
3708 gint imap_get_num_list(Folder *folder, FolderItem *_item, GSList **msgnum_list, gboolean *old_uids_valid)
3709 {
3710         IMAPFolderItem *item = (IMAPFolderItem *)_item;
3711         IMAPSession *session;
3712         gint ok, nummsgs = 0, exists, recent, uid_val, uid_next, unseen;
3713         GSList *uidlist;
3714         gchar *dir;
3715         gboolean selected_folder;
3716
3717         g_return_val_if_fail(folder != NULL, -1);
3718         g_return_val_if_fail(item != NULL, -1);
3719         g_return_val_if_fail(item->item.path != NULL, -1);
3720         g_return_val_if_fail(FOLDER_CLASS(folder) == &imap_class, -1);
3721         g_return_val_if_fail(folder->account != NULL, -1);
3722
3723         session = imap_session_get(folder);
3724         g_return_val_if_fail(session != NULL, -1);
3725
3726         selected_folder = (session->mbox != NULL) &&
3727                           (!strcmp(session->mbox, item->item.path));
3728         if (selected_folder) {
3729                 ok = imap_cmd_noop(session);
3730                 if (ok != IMAP_SUCCESS)
3731                         return -1;
3732                 exists = session->exists;
3733
3734                 *old_uids_valid = TRUE;
3735         } else {
3736                 ok = imap_status(session, IMAP_FOLDER(folder), item->item.path,
3737                                  &exists, &recent, &uid_next, &uid_val, &unseen);
3738                 if (ok != IMAP_SUCCESS)
3739                         return -1;
3740
3741                 if(item->item.mtime == uid_val)
3742                         *old_uids_valid = TRUE;
3743                 else {
3744                         *old_uids_valid = FALSE;
3745
3746                         debug_print("Freeing imap uid cache\n");
3747                         item->lastuid = 0;
3748                         g_slist_free(item->uid_list);
3749                         item->uid_list = NULL;
3750                 
3751                         item->item.mtime = uid_val;
3752
3753                         imap_delete_all_cached_messages((FolderItem *)item);
3754                 }
3755         }
3756
3757         if (!selected_folder)
3758                 item->uid_next = uid_next;
3759
3760         /* If old uid_next matches new uid_next we can be sure no message
3761            was added to the folder */
3762         if (( selected_folder && !session->folder_content_changed) ||
3763             (!selected_folder && uid_next == item->uid_next)) {
3764                 nummsgs = g_slist_length(item->uid_list);
3765
3766                 /* If number of messages is still the same we
3767                    know our caches message numbers are still valid,
3768                    otherwise if the number of messages has decrease
3769                    we discard our cache to start a new scan to find
3770                    out which numbers have been removed */
3771                 if (exists == nummsgs) {
3772                         *msgnum_list = g_slist_copy(item->uid_list);
3773                         return nummsgs;
3774                 } else if (exists < nummsgs) {
3775                         debug_print("Freeing imap uid cache");
3776                         item->lastuid = 0;
3777                         g_slist_free(item->uid_list);
3778                         item->uid_list = NULL;
3779                 }
3780         }
3781
3782         if (exists == 0) {
3783                 *msgnum_list = NULL;
3784                 return 0;
3785         }
3786
3787         nummsgs = get_list_of_uids(folder, item, &uidlist);
3788
3789         if (nummsgs != exists) {
3790                 /* Cache contains more messages then folder, we have cached
3791                    an old UID of a message that was removed and new messages
3792                    have been added too, otherwise the uid_next check would
3793                    not have failed */
3794                 debug_print("Freeing imap uid cache");
3795                 item->lastuid = 0;
3796                 g_slist_free(item->uid_list);
3797                 item->uid_list = NULL;
3798
3799                 g_slist_free(*msgnum_list);
3800
3801                 nummsgs = get_list_of_uids(folder, item, &uidlist);
3802         }
3803
3804         *msgnum_list = uidlist;
3805
3806         dir = folder_item_get_path((FolderItem *)item);
3807         debug_print("removing old messages from %s\n", dir);
3808         remove_numbered_files_not_in_list(dir, *msgnum_list);
3809         g_free(dir);
3810
3811         return nummsgs;
3812 }
3813
3814 static MsgInfo *imap_parse_msg(const gchar *file, FolderItem *item)
3815 {
3816         MsgInfo *msginfo;
3817         MsgFlags flags;
3818
3819         flags.perm_flags = MSG_NEW|MSG_UNREAD;
3820         flags.tmp_flags = 0;
3821
3822         g_return_val_if_fail(item != NULL, NULL);
3823         g_return_val_if_fail(file != NULL, NULL);
3824
3825         if (item->stype == F_QUEUE) {
3826                 MSG_SET_TMP_FLAGS(flags, MSG_QUEUED);
3827         } else if (item->stype == F_DRAFT) {
3828                 MSG_SET_TMP_FLAGS(flags, MSG_DRAFT);
3829         }
3830
3831         msginfo = procheader_parse_file(file, flags, FALSE, FALSE);
3832         if (!msginfo) return NULL;
3833
3834         msginfo->folder = item;
3835
3836         return msginfo;
3837 }
3838
3839 GSList *imap_get_msginfos(Folder *folder, FolderItem *item, GSList *msgnum_list)
3840 {
3841         IMAPSession *session;
3842         MsgInfoList *ret = NULL;
3843         gint ok;
3844
3845         g_return_val_if_fail(folder != NULL, NULL);
3846         g_return_val_if_fail(item != NULL, NULL);
3847         g_return_val_if_fail(msgnum_list != NULL, NULL);
3848
3849         session = imap_session_get(folder);
3850         g_return_val_if_fail(session != NULL, NULL);
3851
3852         ok = imap_select(session, IMAP_FOLDER(folder), item->path,
3853                          NULL, NULL, NULL, NULL);
3854         if (ok != IMAP_SUCCESS)
3855                 return NULL;
3856
3857         if (!(item->stype == F_QUEUE || item->stype == F_DRAFT)) {
3858                 ret = g_slist_concat(ret,
3859                         imap_get_uncached_messages(
3860                         session, item, msgnum_list));
3861         } else {
3862                 MsgNumberList *sorted_list, *elem;
3863                 gint startnum, lastnum;
3864
3865                 sorted_list = g_slist_sort(g_slist_copy(msgnum_list), g_int_compare);
3866
3867                 startnum = lastnum = GPOINTER_TO_INT(sorted_list->data);
3868
3869                 for (elem = sorted_list;; elem = g_slist_next(elem)) {
3870                         guint num;
3871
3872                         if (elem)
3873                                 num = GPOINTER_TO_INT(elem->data);
3874
3875                         if (num > lastnum + 1 || elem == NULL) {
3876                                 int i;
3877                                 for (i = startnum; i <= lastnum; ++i) {
3878                                         gchar *file;
3879                         
3880                                         file = imap_fetch_msg(folder, item, i);
3881                                         if (file != NULL) {
3882                                                 MsgInfo *msginfo = imap_parse_msg(file, item);
3883                                                 if (msginfo != NULL) {
3884                                                         msginfo->msgnum = i;
3885                                                         ret = g_slist_append(ret, msginfo);
3886                                                 }
3887                                                 g_free(file);
3888                                         }
3889                                 }
3890
3891                                 if (elem == NULL)
3892                                         break;
3893
3894                                 startnum = num;
3895                         }
3896                         lastnum = num;
3897                 }
3898
3899                 g_slist_free(sorted_list);
3900         }
3901
3902         return ret;
3903 }
3904
3905 MsgInfo *imap_get_msginfo(Folder *folder, FolderItem *item, gint uid)
3906 {
3907         MsgInfo *msginfo = NULL;
3908         MsgInfoList *msginfolist;
3909         MsgNumberList numlist;
3910
3911         numlist.next = NULL;
3912         numlist.data = GINT_TO_POINTER(uid);
3913
3914         msginfolist = imap_get_msginfos(folder, item, &numlist);
3915         if (msginfolist != NULL) {
3916                 msginfo = msginfolist->data;
3917                 g_slist_free(msginfolist);
3918         }
3919
3920         return msginfo;
3921 }
3922
3923 gboolean imap_scan_required(Folder *folder, FolderItem *_item)
3924 {
3925         IMAPSession *session;
3926         IMAPFolderItem *item = (IMAPFolderItem *)_item;
3927         gint ok, exists = 0, recent = 0, unseen = 0;
3928         guint32 uid_next, uid_val = 0;
3929         gboolean selected_folder;
3930         
3931         g_return_val_if_fail(folder != NULL, FALSE);
3932         g_return_val_if_fail(item != NULL, FALSE);
3933         g_return_val_if_fail(item->item.folder != NULL, FALSE);
3934         g_return_val_if_fail(FOLDER_CLASS(item->item.folder) == &imap_class, FALSE);
3935
3936         if (item->item.path == NULL)
3937                 return FALSE;
3938
3939         session = imap_session_get(folder);
3940         g_return_val_if_fail(session != NULL, FALSE);
3941
3942         selected_folder = (session->mbox != NULL) &&
3943                           (!strcmp(session->mbox, item->item.path));
3944         if (selected_folder) {
3945                 ok = imap_cmd_noop(session);
3946                 if (ok != IMAP_SUCCESS)
3947                         return FALSE;
3948
3949                 if (session->folder_content_changed)
3950                         return TRUE;
3951         } else {
3952                 ok = imap_status(session, IMAP_FOLDER(folder), item->item.path,
3953                                  &exists, &recent, &uid_next, &uid_val, &unseen);
3954                 if (ok != IMAP_SUCCESS)
3955                         return FALSE;
3956
3957                 if ((uid_next != item->uid_next) || (exists < item->item.total_msgs))
3958                         return TRUE;
3959         }
3960
3961         return FALSE;
3962 }
3963
3964 void imap_change_flags(Folder *folder, FolderItem *item, MsgInfo *msginfo, MsgPermFlags newflags)
3965 {
3966         IMAPSession *session;
3967         IMAPFlags flags_set = 0, flags_unset = 0;
3968         gint ok = IMAP_SUCCESS;
3969         MsgNumberList numlist;
3970         
3971         g_return_if_fail(folder != NULL);
3972         g_return_if_fail(folder->klass == &imap_class);
3973         g_return_if_fail(item != NULL);
3974         g_return_if_fail(item->folder == folder);
3975         g_return_if_fail(msginfo != NULL);
3976         g_return_if_fail(msginfo->folder == item);
3977
3978         session = imap_session_get(folder);
3979         if (!session) return;
3980
3981         if ((ok = imap_select(session, IMAP_FOLDER(folder), msginfo->folder->path,
3982             NULL, NULL, NULL, NULL)) != IMAP_SUCCESS)
3983                 return;
3984
3985         if (!MSG_IS_MARKED(msginfo->flags) &&  (newflags & MSG_MARKED))
3986                 flags_set |= IMAP_FLAG_FLAGGED;
3987         if ( MSG_IS_MARKED(msginfo->flags) && !(newflags & MSG_MARKED))
3988                 flags_unset |= IMAP_FLAG_FLAGGED;
3989
3990         if (!MSG_IS_UNREAD(msginfo->flags) &&  (newflags & MSG_UNREAD))
3991                 flags_unset |= IMAP_FLAG_SEEN;
3992         if ( MSG_IS_UNREAD(msginfo->flags) && !(newflags & MSG_UNREAD))
3993                 flags_set |= IMAP_FLAG_SEEN;
3994
3995         if (!MSG_IS_REPLIED(msginfo->flags) &&  (newflags & MSG_REPLIED))
3996                 flags_set |= IMAP_FLAG_ANSWERED;
3997         if ( MSG_IS_REPLIED(msginfo->flags) && !(newflags & MSG_REPLIED))
3998                 flags_set |= IMAP_FLAG_ANSWERED;
3999
4000         numlist.next = NULL;
4001         numlist.data = GINT_TO_POINTER(msginfo->msgnum);
4002         
4003         if (flags_set) {
4004                 ok = imap_set_message_flags(session, &numlist, flags_set, TRUE);
4005                 if (ok != IMAP_SUCCESS) return;
4006         }
4007
4008         if (flags_unset) {
4009                 ok = imap_set_message_flags(session, &numlist, flags_unset, FALSE);
4010                 if (ok != IMAP_SUCCESS) return;
4011         }
4012
4013         msginfo->flags.perm_flags = newflags;
4014
4015         return;
4016 }