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