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