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