0647be42d92b9556b35903104640fc0294e7edd1
[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) {
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 %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         if (session) {
689                 session->last_access_time = time(NULL);
690         }
691         return IMAP_SESSION(session);
692 }
693
694 static IMAPSession *imap_session_new(const PrefsAccount *account)
695 {
696         IMAPSession *session;
697         SockInfo *imap_sock;
698         gushort port;
699
700 #ifdef USE_OPENSSL
701         /* FIXME: IMAP over SSL only... */ 
702         SSLType ssl_type;
703
704         port = account->set_imapport ? account->imapport
705                 : account->ssl_imap == SSL_TUNNEL ? IMAPS_PORT : IMAP4_PORT;
706         ssl_type = account->ssl_imap;   
707 #else
708         port = account->set_imapport ? account->imapport
709                 : IMAP4_PORT;
710 #endif
711
712         if (account->set_tunnelcmd) {
713                 log_message(_("creating tunneled IMAP4 connection\n"));
714 #if USE_OPENSSL
715                 if ((imap_sock = imap_open_tunnel(account->recv_server, 
716                                                   account->tunnelcmd,
717                                                   ssl_type)) == NULL)
718 #else
719                 if ((imap_sock = imap_open_tunnel(account->recv_server, 
720                                                   account->tunnelcmd)) == NULL)
721 #endif
722                         return NULL;
723         } else {
724                 g_return_val_if_fail(account->recv_server != NULL, NULL);
725
726                 log_message(_("creating IMAP4 connection to %s:%d ...\n"),
727                             account->recv_server, port);
728                 
729 #if USE_OPENSSL
730                 if ((imap_sock = imap_open(account->recv_server, port,
731                                            ssl_type)) == NULL)
732 #else
733                 if ((imap_sock = imap_open(account->recv_server, port)) == NULL)
734 #endif
735                         return NULL;
736         }
737
738         session = g_new0(IMAPSession, 1);
739         session_init(SESSION(session));
740         SESSION(session)->type             = SESSION_IMAP;
741         SESSION(session)->server           = g_strdup(account->recv_server);
742         SESSION(session)->sock             = imap_sock;
743
744         SESSION(session)->destroy          = imap_session_destroy;
745
746         session->capability = NULL;
747
748         session->authenticated = FALSE;
749         session->mbox = NULL;
750         session->cmd_count = 0;
751
752         /* Only need to log in if the connection was not PREAUTH */
753         if (imap_greeting(session) != IMAP_SUCCESS) {
754                 session_destroy(SESSION(session));
755                 return NULL;
756         }
757
758 #if USE_OPENSSL
759         if (account->ssl_imap == SSL_STARTTLS && 
760             imap_has_capability(session, "STARTTLS")) {
761                 gint ok;
762
763                 ok = imap_cmd_starttls(session);
764                 if (ok != IMAP_SUCCESS) {
765                         log_warning(_("Can't start TLS session.\n"));
766                         session_destroy(SESSION(session));
767                         return NULL;
768                 }
769                 if (!ssl_init_socket_with_method(SESSION(session)->sock, 
770                     SSL_METHOD_TLSv1)) {
771                         session_destroy(SESSION(session));
772                         return NULL;
773                 }
774
775                 imap_free_capabilities(session);
776                 session->authenticated = FALSE;
777                 session->uidplus = FALSE;
778                 session->cmd_count = 1;
779
780                 if (imap_greeting(session) != IMAP_SUCCESS) {
781                         session_destroy(SESSION(session));
782                         return NULL;
783                 }               
784         }
785 #endif
786         log_message("IMAP connection is %s-authenticated\n",
787                     (session->authenticated) ? "pre" : "un");
788
789         return session;
790 }
791
792 static void imap_session_authenticate(IMAPSession *session, 
793                                       const PrefsAccount *account)
794 {
795         gchar *pass;
796
797         g_return_if_fail(account->userid != NULL);
798
799         pass = account->passwd;
800         if (!pass) {
801                 gchar *tmp_pass;
802                 tmp_pass = input_dialog_query_password(account->recv_server, account->userid);
803                 if (!tmp_pass)
804                         return;
805                 Xstrdup_a(pass, tmp_pass, {g_free(tmp_pass); return;});
806                 g_free(tmp_pass);
807         }
808
809         if (imap_auth(session, account->userid, pass, account->imap_auth_type) != IMAP_SUCCESS) {
810                 imap_cmd_logout(session);
811                 return;
812         }
813
814         session->authenticated = TRUE;
815 }
816
817 static void imap_session_destroy(Session *session)
818 {
819         imap_free_capabilities(IMAP_SESSION(session));
820         g_free(IMAP_SESSION(session)->mbox);
821         sock_close(session->sock);
822         session->sock = NULL;
823 }
824
825 static gchar *imap_fetch_msg(Folder *folder, FolderItem *item, gint uid)
826 {
827         gchar *path, *filename;
828         IMAPSession *session;
829         gint ok;
830
831         g_return_val_if_fail(folder != NULL, NULL);
832         g_return_val_if_fail(item != NULL, NULL);
833
834         path = folder_item_get_path(item);
835         if (!is_dir_exist(path))
836                 make_dir_hier(path);
837         filename = g_strconcat(path, G_DIR_SEPARATOR_S, itos(uid), NULL);
838         g_free(path);
839
840         if (is_file_exist(filename)) {
841                 debug_print("message %d has been already cached.\n", uid);
842                 return filename;
843         }
844
845         session = imap_session_get(folder);
846         if (!session) {
847                 g_free(filename);
848                 return NULL;
849         }
850
851         ok = imap_select(session, IMAP_FOLDER(folder), item->path,
852                          NULL, NULL, NULL, NULL);
853         if (ok != IMAP_SUCCESS) {
854                 g_warning("can't select mailbox %s\n", item->path);
855                 g_free(filename);
856                 return NULL;
857         }
858
859         debug_print("getting message %d...\n", uid);
860         ok = imap_cmd_fetch(session, (guint32)uid, filename);
861
862         if (ok != IMAP_SUCCESS) {
863                 g_warning("can't fetch message %d\n", uid);
864                 g_free(filename);
865                 return NULL;
866         }
867
868         return filename;
869 }
870
871 static gint imap_add_msg(Folder *folder, FolderItem *dest, 
872                          const gchar *file, MsgFlags *flags)
873 {
874         gint ret;
875         GSList file_list;
876         MsgFileInfo fileinfo;
877
878         g_return_val_if_fail(file != NULL, -1);
879
880         fileinfo.msginfo = NULL;
881         fileinfo.file = (gchar *)file;
882         fileinfo.flags = flags;
883         file_list.data = &fileinfo;
884         file_list.next = NULL;
885
886         ret = imap_add_msgs(folder, dest, &file_list, NULL);
887         return ret;
888 }
889
890 static gint imap_add_msgs(Folder *folder, FolderItem *dest, GSList *file_list,
891                    GRelation *relation)
892 {
893         gchar *destdir;
894         IMAPSession *session;
895         guint32 last_uid = 0;
896         GSList *cur;
897         MsgFileInfo *fileinfo;
898         gint ok;
899
900         g_return_val_if_fail(folder != NULL, -1);
901         g_return_val_if_fail(dest != NULL, -1);
902         g_return_val_if_fail(file_list != NULL, -1);
903
904         session = imap_session_get(folder);
905         if (!session) return -1;
906
907         destdir = imap_get_real_path(IMAP_FOLDER(folder), dest->path);
908
909         for (cur = file_list; cur != NULL; cur = cur->next) {
910                 IMAPFlags iflags = 0;
911                 guint32 new_uid = 0;
912
913                 fileinfo = (MsgFileInfo *)cur->data;
914
915                 if (fileinfo->flags) {
916                         if (MSG_IS_MARKED(*fileinfo->flags))
917                                 iflags |= IMAP_FLAG_FLAGGED;
918                         if (MSG_IS_REPLIED(*fileinfo->flags))
919                                 iflags |= IMAP_FLAG_ANSWERED;
920                         if (!MSG_IS_UNREAD(*fileinfo->flags))
921                                 iflags |= IMAP_FLAG_SEEN;
922                 }
923
924                 if (dest->stype == F_OUTBOX ||
925                     dest->stype == F_QUEUE  ||
926                     dest->stype == F_DRAFT  ||
927                     dest->stype == F_TRASH)
928                         iflags |= IMAP_FLAG_SEEN;
929
930                 ok = imap_cmd_append(session, destdir, fileinfo->file, iflags, 
931                                      &new_uid);
932
933                 if (ok != IMAP_SUCCESS) {
934                         g_warning("can't append message %s\n", fileinfo->file);
935                         g_free(destdir);
936                         return -1;
937                 }
938
939                 if (relation != NULL)
940                         g_relation_insert(relation, fileinfo->msginfo != NULL ? 
941                                           (gpointer) fileinfo->msginfo : (gpointer) fileinfo,
942                                           GINT_TO_POINTER(dest->last_num + 1));
943                 if (last_uid < new_uid)
944                         last_uid = new_uid;
945         }
946
947         g_free(destdir);
948
949         return last_uid;
950 }
951
952 static gint imap_do_copy_msgs(Folder *folder, FolderItem *dest, 
953                               MsgInfoList *msglist, GRelation *relation)
954 {
955         FolderItem *src;
956         gchar *destdir;
957         GSList *seq_list, *cur;
958         MsgInfo *msginfo;
959         IMAPSession *session;
960         gint ok = IMAP_SUCCESS;
961         GRelation *uid_mapping;
962         gint last_num = 0;
963         
964         g_return_val_if_fail(folder != NULL, -1);
965         g_return_val_if_fail(dest != NULL, -1);
966         g_return_val_if_fail(msglist != NULL, -1);
967
968         session = imap_session_get(folder);
969         if (!session) return -1;
970
971         msginfo = (MsgInfo *)msglist->data;
972
973         src = msginfo->folder;
974         if (src == dest) {
975                 g_warning("the src folder is identical to the dest.\n");
976                 return -1;
977         }
978
979         ok = imap_select(session, IMAP_FOLDER(folder), msginfo->folder->path,
980                          NULL, NULL, NULL, NULL);
981         if (ok != IMAP_SUCCESS)
982                 return ok;
983
984         destdir = imap_get_real_path(IMAP_FOLDER(folder), dest->path);
985         seq_list = imap_get_seq_set_from_msglist(msglist);
986         uid_mapping = g_relation_new(2);
987         g_relation_index(uid_mapping, 0, g_direct_hash, g_direct_equal);
988         
989         for (cur = seq_list; cur != NULL; cur = g_slist_next(cur)) {
990                 gchar *seq_set = (gchar *)cur->data;
991
992                 debug_print("Copying message %s%c[%s] to %s ...\n",
993                             src->path, G_DIR_SEPARATOR,
994                             seq_set, destdir);
995
996                 ok = imap_cmd_copy(session, seq_set, destdir, uid_mapping);
997                 if (ok != IMAP_SUCCESS) {
998                         g_relation_destroy(uid_mapping);
999                         imap_seq_set_free(seq_list);
1000                         return -1;
1001                 }
1002         }
1003
1004         for (cur = msglist; cur != NULL; cur = g_slist_next(cur)) {
1005                 MsgInfo *msginfo = (MsgInfo *)cur->data;
1006                 GTuples *tuples;
1007
1008                 tuples = g_relation_select(uid_mapping, 
1009                                            GINT_TO_POINTER(msginfo->msgnum),
1010                                            0);
1011                 if (tuples->len > 0) {
1012                         gint num = GPOINTER_TO_INT(g_tuples_index(tuples, 0, 1));
1013                         g_relation_insert(relation, msginfo,
1014                                           GPOINTER_TO_INT(num));
1015                         if (num > last_num)
1016                                 last_num = num;
1017                 } else
1018                         g_relation_insert(relation, msginfo,
1019                                           GPOINTER_TO_INT(0));
1020                 g_tuples_destroy(tuples);
1021         }
1022
1023         g_relation_destroy(uid_mapping);
1024         imap_seq_set_free(seq_list);
1025
1026         g_free(destdir);
1027
1028         if (ok == IMAP_SUCCESS)
1029                 return last_num;
1030         else
1031                 return -1;
1032 }
1033
1034 static gint imap_copy_msg(Folder *folder, FolderItem *dest, MsgInfo *msginfo)
1035 {
1036         GSList msglist;
1037
1038         g_return_val_if_fail(msginfo != NULL, -1);
1039
1040         msglist.data = msginfo;
1041         msglist.next = NULL;
1042
1043         return imap_copy_msgs(folder, dest, &msglist, NULL);
1044 }
1045
1046 static gint imap_copy_msgs(Folder *folder, FolderItem *dest, 
1047                     MsgInfoList *msglist, GRelation *relation)
1048 {
1049         MsgInfo *msginfo;
1050         GSList *file_list;
1051         gint ret;
1052
1053         g_return_val_if_fail(folder != NULL, -1);
1054         g_return_val_if_fail(dest != NULL, -1);
1055         g_return_val_if_fail(msglist != NULL, -1);
1056
1057         msginfo = (MsgInfo *)msglist->data;
1058         g_return_val_if_fail(msginfo->folder != NULL, -1);
1059
1060         if (folder == msginfo->folder->folder)
1061                 return imap_do_copy_msgs(folder, dest, msglist, relation);
1062
1063         file_list = procmsg_get_message_file_list(msglist);
1064         g_return_val_if_fail(file_list != NULL, -1);
1065
1066         ret = imap_add_msgs(folder, dest, file_list, relation);
1067
1068         procmsg_message_file_list_free(file_list);
1069
1070         return ret;
1071 }
1072
1073 static gint imap_remove_msg(Folder *folder, FolderItem *item, gint uid)
1074 {
1075         gint ok;
1076         IMAPSession *session;
1077         gchar *dir;
1078         MsgNumberList numlist;
1079         
1080         g_return_val_if_fail(folder != NULL, -1);
1081         g_return_val_if_fail(FOLDER_CLASS(folder) == &imap_class, -1);
1082         g_return_val_if_fail(item != NULL, -1);
1083
1084         session = imap_session_get(folder);
1085         if (!session) return -1;
1086
1087         ok = imap_select(session, IMAP_FOLDER(folder), item->path,
1088                          NULL, NULL, NULL, NULL);
1089         if (ok != IMAP_SUCCESS)
1090                 return ok;
1091
1092         numlist.next = NULL;
1093         numlist.data = GINT_TO_POINTER(uid);
1094         
1095         ok = imap_set_message_flags
1096                 (IMAP_SESSION(REMOTE_FOLDER(folder)->session),
1097                 &numlist, IMAP_FLAG_DELETED, TRUE);
1098         if (ok != IMAP_SUCCESS) {
1099                 log_warning(_("can't set deleted flags: %d\n"), uid);
1100                 return ok;
1101         }
1102
1103         if (!session->uidplus) {
1104                 ok = imap_cmd_expunge(session, NULL);
1105         } else {
1106                 gchar *uidstr;
1107
1108                 uidstr = g_strdup_printf("%u", uid);
1109                 ok = imap_cmd_expunge(session, uidstr);
1110                 g_free(uidstr);
1111         }
1112         if (ok != IMAP_SUCCESS) {
1113                 log_warning(_("can't expunge\n"));
1114                 return ok;
1115         }
1116
1117         IMAP_FOLDER_ITEM(item)->uid_list = g_slist_remove(
1118             IMAP_FOLDER_ITEM(item)->uid_list, numlist.data);
1119         dir = folder_item_get_path(item);
1120         if (is_dir_exist(dir))
1121                 remove_numbered_files(dir, uid, uid);
1122         g_free(dir);
1123
1124         return IMAP_SUCCESS;
1125 }
1126
1127 static gint imap_remove_all_msg(Folder *folder, FolderItem *item)
1128 {
1129         gint ok;
1130         IMAPSession *session;
1131         gchar *dir;
1132
1133         g_return_val_if_fail(folder != NULL, -1);
1134         g_return_val_if_fail(item != NULL, -1);
1135
1136         session = imap_session_get(folder);
1137         if (!session) return -1;
1138
1139         ok = imap_select(session, IMAP_FOLDER(folder), item->path,
1140                          NULL, NULL, NULL, NULL);
1141         if (ok != IMAP_SUCCESS)
1142                 return ok;
1143
1144         imap_gen_send(session, "STORE 1:* +FLAGS.SILENT (\\Deleted)");
1145         ok = imap_cmd_ok(session, NULL);
1146         if (ok != IMAP_SUCCESS) {
1147                 log_warning(_("can't set deleted flags: 1:*\n"));
1148                 return ok;
1149         }
1150
1151         ok = imap_cmd_expunge(session, NULL);
1152         if (ok != IMAP_SUCCESS) {
1153                 log_warning(_("can't expunge\n"));
1154                 return ok;
1155         }
1156
1157         dir = folder_item_get_path(item);
1158         if (is_dir_exist(dir))
1159                 remove_all_numbered_files(dir);
1160         g_free(dir);
1161
1162         return IMAP_SUCCESS;
1163 }
1164
1165 static gboolean imap_is_msg_changed(Folder *folder, FolderItem *item,
1166                                     MsgInfo *msginfo)
1167 {
1168         /* TODO: properly implement this method */
1169         return FALSE;
1170 }
1171
1172 static gint imap_close(Folder *folder, FolderItem *item)
1173 {
1174         gint ok;
1175         IMAPSession *session;
1176
1177         g_return_val_if_fail(folder != NULL, -1);
1178         g_return_val_if_fail(item != NULL, -1);
1179         g_return_val_if_fail(item->path != NULL, -1);
1180
1181         session = imap_session_get(folder);
1182         if (!session) return -1;
1183
1184         if (session->mbox) {
1185                 if (strcmp2(session->mbox, item->path) != 0) return -1;
1186
1187                 ok = imap_cmd_close(session);
1188                 if (ok != IMAP_SUCCESS)
1189                         log_warning(_("can't close folder\n"));
1190
1191                 g_free(session->mbox);
1192                 session->mbox = NULL;
1193
1194                 return ok;
1195         }
1196
1197         return 0;
1198 }
1199
1200 static gint imap_scan_tree(Folder *folder)
1201 {
1202         FolderItem *item = NULL;
1203         IMAPSession *session;
1204         gchar *root_folder = NULL;
1205
1206         g_return_val_if_fail(folder != NULL, -1);
1207         g_return_val_if_fail(folder->account != NULL, -1);
1208
1209         session = imap_session_get(folder);
1210         if (!session) {
1211                 if (!folder->node) {
1212                         folder_tree_destroy(folder);
1213                         item = folder_item_new(folder, folder->name, NULL);
1214                         item->folder = folder;
1215                         folder->node = item->node = g_node_new(item);
1216                 }
1217                 return -1;
1218         }
1219
1220         if (folder->account->imap_dir && *folder->account->imap_dir) {
1221                 gchar *real_path;
1222                 GPtrArray *argbuf;
1223                 gint ok;
1224
1225                 Xstrdup_a(root_folder, folder->account->imap_dir, return -1);
1226                 extract_quote(root_folder, '"');
1227                 subst_char(root_folder,
1228                            imap_get_path_separator(IMAP_FOLDER(folder),
1229                                                    root_folder),
1230                            '/');
1231                 strtailchomp(root_folder, '/');
1232                 real_path = imap_get_real_path
1233                         (IMAP_FOLDER(folder), root_folder);
1234                 debug_print("IMAP root directory: %s\n", real_path);
1235
1236                 /* check if root directory exist */
1237                 argbuf = g_ptr_array_new();
1238                 ok = imap_cmd_list(session, NULL, real_path, argbuf);
1239                 if (ok != IMAP_SUCCESS ||
1240                     search_array_str(argbuf, "LIST ") == NULL) {
1241                         log_warning(_("root folder %s does not exist\n"), real_path);
1242                         g_ptr_array_free(argbuf, TRUE);
1243                         g_free(real_path);
1244
1245                         if (!folder->node) {
1246                                 item = folder_item_new(folder, folder->name, NULL);
1247                                 item->folder = folder;
1248                                 folder->node = item->node = g_node_new(item);
1249                         }
1250                         return -1;
1251                 }
1252                 g_ptr_array_free(argbuf, TRUE);
1253                 g_free(real_path);
1254         }
1255
1256         if (folder->node)
1257                 item = FOLDER_ITEM(folder->node->data);
1258         if (!item || ((item->path || root_folder) &&
1259                       strcmp2(item->path, root_folder) != 0)) {
1260                 folder_tree_destroy(folder);
1261                 item = folder_item_new(folder, folder->name, root_folder);
1262                 item->folder = folder;
1263                 folder->node = item->node = g_node_new(item);
1264         }
1265
1266         imap_scan_tree_recursive(session, FOLDER_ITEM(folder->node->data));
1267         imap_create_missing_folders(folder);
1268
1269         return 0;
1270 }
1271
1272 static gint imap_scan_tree_recursive(IMAPSession *session, FolderItem *item)
1273 {
1274         Folder *folder;
1275         IMAPFolder *imapfolder;
1276         FolderItem *new_item;
1277         GSList *item_list, *cur;
1278         GNode *node;
1279         gchar *real_path;
1280         gchar *wildcard_path;
1281         gchar separator;
1282         gchar wildcard[3];
1283
1284         g_return_val_if_fail(item != NULL, -1);
1285         g_return_val_if_fail(item->folder != NULL, -1);
1286         g_return_val_if_fail(item->no_sub == FALSE, -1);
1287
1288         folder = item->folder;
1289         imapfolder = IMAP_FOLDER(folder);
1290
1291         separator = imap_get_path_separator(imapfolder, item->path);
1292
1293         if (folder->ui_func)
1294                 folder->ui_func(folder, item, folder->ui_func_data);
1295
1296         if (item->path) {
1297                 wildcard[0] = separator;
1298                 wildcard[1] = '%';
1299                 wildcard[2] = '\0';
1300                 real_path = imap_get_real_path(imapfolder, item->path);
1301         } else {
1302                 wildcard[0] = '%';
1303                 wildcard[1] = '\0';
1304                 real_path = g_strdup("");
1305         }
1306
1307         Xstrcat_a(wildcard_path, real_path, wildcard,
1308                   {g_free(real_path); return IMAP_ERROR;});
1309         QUOTE_IF_REQUIRED(wildcard_path, wildcard_path);
1310
1311         imap_gen_send(session, "LIST \"\" %s",
1312                       wildcard_path);
1313
1314         strtailchomp(real_path, separator);
1315         item_list = imap_parse_list(imapfolder, session, real_path, NULL);
1316         g_free(real_path);
1317
1318         node = item->node->children;
1319         while (node != NULL) {
1320                 FolderItem *old_item = FOLDER_ITEM(node->data);
1321                 GNode *next = node->next;
1322
1323                 new_item = NULL;
1324                 for (cur = item_list; cur != NULL; cur = cur->next) {
1325                         FolderItem *cur_item = FOLDER_ITEM(cur->data);
1326                         if (!strcmp2(old_item->path, cur_item->path)) {
1327                                 new_item = cur_item;
1328                                 break;
1329                         }
1330                 }
1331                 if (!new_item) {
1332                         debug_print("folder '%s' not found. removing...\n",
1333                                     old_item->path);
1334                         folder_item_remove(old_item);
1335                 } else {
1336                         old_item->no_sub = new_item->no_sub;
1337                         old_item->no_select = new_item->no_select;
1338                         if (old_item->no_sub == TRUE && node->children) {
1339                                 debug_print("folder '%s' doesn't have "
1340                                             "subfolders. removing...\n",
1341                                             old_item->path);
1342                                 folder_item_remove_children(old_item);
1343                         }
1344                 }
1345
1346                 node = next;
1347         }
1348
1349         for (cur = item_list; cur != NULL; cur = cur->next) {
1350                 FolderItem *cur_item = FOLDER_ITEM(cur->data);
1351                 new_item = NULL;
1352                 for (node = item->node->children; node != NULL;
1353                      node = node->next) {
1354                         if (!strcmp2(FOLDER_ITEM(node->data)->path,
1355                                      cur_item->path)) {
1356                                 new_item = FOLDER_ITEM(node->data);
1357                                 folder_item_destroy(cur_item);
1358                                 cur_item = NULL;
1359                                 break;
1360                         }
1361                 }
1362                 if (!new_item) {
1363                         new_item = cur_item;
1364                         debug_print("new folder '%s' found.\n", new_item->path);
1365                         folder_item_append(item, new_item);
1366                 }
1367
1368                 if (!strcmp(new_item->path, "INBOX")) {
1369                         new_item->stype = F_INBOX;
1370                         folder->inbox = new_item;
1371                 } else if (!folder_item_parent(item) || item->stype == F_INBOX) {
1372                         gchar *base;
1373
1374                         base = g_basename(new_item->path);
1375
1376                         if (!folder->outbox && !strcasecmp(base, "Sent")) {
1377                                 new_item->stype = F_OUTBOX;
1378                                 folder->outbox = new_item;
1379                         } else if (!folder->draft && !strcasecmp(base, "Drafts")) {
1380                                 new_item->stype = F_DRAFT;
1381                                 folder->draft = new_item;
1382                         } else if (!folder->queue && !strcasecmp(base, "Queue")) {
1383                                 new_item->stype = F_QUEUE;
1384                                 folder->queue = new_item;
1385                         } else if (!folder->trash && !strcasecmp(base, "Trash")) {
1386                                 new_item->stype = F_TRASH;
1387                                 folder->trash = new_item;
1388                         }
1389                 }
1390
1391                 if (new_item->no_sub == FALSE)
1392                         imap_scan_tree_recursive(session, new_item);
1393         }
1394
1395         g_slist_free(item_list);
1396
1397         return IMAP_SUCCESS;
1398 }
1399
1400 static GSList *imap_parse_list(IMAPFolder *folder, IMAPSession *session,
1401                                const gchar *real_path, gchar *separator)
1402 {
1403         gchar buf[IMAPBUFSIZE];
1404         gchar flags[256];
1405         gchar separator_str[16];
1406         gchar *p;
1407         gchar *name;
1408         gchar *loc_name, *loc_path;
1409         GSList *item_list = NULL;
1410         GString *str;
1411         FolderItem *new_item;
1412
1413         debug_print("getting list of %s ...\n",
1414                     *real_path ? real_path : "\"\"");
1415
1416         str = g_string_new(NULL);
1417
1418         for (;;) {
1419                 if (sock_gets(SESSION(session)->sock, buf, sizeof(buf)) <= 0) {
1420                         log_warning(_("error occurred while getting LIST.\n"));
1421                         break;
1422                 }
1423                 strretchomp(buf);
1424                 if (buf[0] != '*' || buf[1] != ' ') {
1425                         log_print("IMAP4< %s\n", buf);
1426                         if (sscanf(buf, "%*d %16s", buf) < 1 ||
1427                             strcmp(buf, "OK") != 0)
1428                                 log_warning(_("error occurred while getting LIST.\n"));
1429                                 
1430                         break;
1431                 }
1432                 debug_print("IMAP4< %s\n", buf);
1433
1434                 g_string_assign(str, buf);
1435                 p = str->str + 2;
1436                 if (strncmp(p, "LIST ", 5) != 0) continue;
1437                 p += 5;
1438
1439                 if (*p != '(') continue;
1440                 p++;
1441                 p = strchr_cpy(p, ')', flags, sizeof(flags));
1442                 if (!p) continue;
1443                 while (*p == ' ') p++;
1444
1445                 p = strchr_cpy(p, ' ', separator_str, sizeof(separator_str));
1446                 if (!p) continue;
1447                 extract_quote(separator_str, '"');
1448                 if (!strcmp(separator_str, "NIL"))
1449                         separator_str[0] = '\0';
1450                 if (separator)
1451                         *separator = separator_str[0];
1452
1453                 buf[0] = '\0';
1454                 while (*p == ' ') p++;
1455                 if (*p == '{' || *p == '"')
1456                         p = imap_parse_atom(SESSION(session)->sock, p,
1457                                             buf, sizeof(buf), str);
1458                 else
1459                         strncpy2(buf, p, sizeof(buf));
1460                 strtailchomp(buf, separator_str[0]);
1461                 if (buf[0] == '\0') continue;
1462                 if (!strcmp(buf, real_path)) continue;
1463
1464                 if (separator_str[0] != '\0')
1465                         subst_char(buf, separator_str[0], '/');
1466                 name = g_basename(buf);
1467                 if (name[0] == '.') continue;
1468
1469                 loc_name = imap_modified_utf7_to_locale(name);
1470                 loc_path = imap_modified_utf7_to_locale(buf);
1471                 new_item = folder_item_new(FOLDER(folder), loc_name, loc_path);
1472                 if (strcasestr(flags, "\\Noinferiors") != NULL)
1473                         new_item->no_sub = TRUE;
1474                 if (strcmp(buf, "INBOX") != 0 &&
1475                     strcasestr(flags, "\\Noselect") != NULL)
1476                         new_item->no_select = TRUE;
1477
1478                 item_list = g_slist_append(item_list, new_item);
1479
1480                 debug_print("folder '%s' found.\n", loc_path);
1481                 g_free(loc_path);
1482                 g_free(loc_name);
1483         }
1484
1485         g_string_free(str, TRUE);
1486
1487         return item_list;
1488 }
1489
1490 static gint imap_create_tree(Folder *folder)
1491 {
1492         g_return_val_if_fail(folder != NULL, -1);
1493         g_return_val_if_fail(folder->node != NULL, -1);
1494         g_return_val_if_fail(folder->node->data != NULL, -1);
1495         g_return_val_if_fail(folder->account != NULL, -1);
1496
1497         imap_scan_tree(folder);
1498         imap_create_missing_folders(folder);
1499
1500         return 0;
1501 }
1502
1503 static void imap_create_missing_folders(Folder *folder)
1504 {
1505         g_return_if_fail(folder != NULL);
1506
1507         if (!folder->inbox)
1508                 folder->inbox = imap_create_special_folder
1509                         (folder, F_INBOX, "INBOX");
1510 #if 0
1511         if (!folder->outbox)
1512                 folder->outbox = imap_create_special_folder
1513                         (folder, F_OUTBOX, "Sent");
1514         if (!folder->draft)
1515                 folder->draft = imap_create_special_folder
1516                         (folder, F_DRAFT, "Drafts");
1517         if (!folder->queue)
1518                 folder->queue = imap_create_special_folder
1519                         (folder, F_QUEUE, "Queue");
1520 #endif
1521         if (!folder->trash)
1522                 folder->trash = imap_create_special_folder
1523                         (folder, F_TRASH, "Trash");
1524 }
1525
1526 static FolderItem *imap_create_special_folder(Folder *folder,
1527                                               SpecialFolderItemType stype,
1528                                               const gchar *name)
1529 {
1530         FolderItem *item;
1531         FolderItem *new_item;
1532
1533         g_return_val_if_fail(folder != NULL, NULL);
1534         g_return_val_if_fail(folder->node != NULL, NULL);
1535         g_return_val_if_fail(folder->node->data != NULL, NULL);
1536         g_return_val_if_fail(folder->account != NULL, NULL);
1537         g_return_val_if_fail(name != NULL, NULL);
1538
1539         item = FOLDER_ITEM(folder->node->data);
1540         new_item = imap_create_folder(folder, item, name);
1541
1542         if (!new_item) {
1543                 g_warning("Can't create '%s'\n", name);
1544                 if (!folder->inbox) return NULL;
1545
1546                 new_item = imap_create_folder(folder, folder->inbox, name);
1547                 if (!new_item)
1548                         g_warning("Can't create '%s' under INBOX\n", name);
1549                 else
1550                         new_item->stype = stype;
1551         } else
1552                 new_item->stype = stype;
1553
1554         return new_item;
1555 }
1556
1557 static gchar *imap_folder_get_path(Folder *folder)
1558 {
1559         gchar *folder_path;
1560
1561         g_return_val_if_fail(folder != NULL, NULL);
1562         g_return_val_if_fail(folder->account != NULL, NULL);
1563
1564         folder_path = g_strconcat(get_imap_cache_dir(),
1565                                   G_DIR_SEPARATOR_S,
1566                                   folder->account->recv_server,
1567                                   G_DIR_SEPARATOR_S,
1568                                   folder->account->userid,
1569                                   NULL);
1570
1571         return folder_path;
1572 }
1573
1574 static gchar *imap_item_get_path(Folder *folder, FolderItem *item)
1575 {
1576         gchar *folder_path, *path;
1577
1578         g_return_val_if_fail(folder != NULL, NULL);
1579         g_return_val_if_fail(item != NULL, NULL);
1580         folder_path = imap_folder_get_path(folder);
1581
1582         g_return_val_if_fail(folder_path != NULL, NULL);
1583         if (folder_path[0] == G_DIR_SEPARATOR) {
1584                 if (item->path)
1585                         path = g_strconcat(folder_path, G_DIR_SEPARATOR_S,
1586                                            item->path, NULL);
1587                 else
1588                         path = g_strdup(folder_path);
1589         } else {
1590                 if (item->path)
1591                         path = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S,
1592                                            folder_path, G_DIR_SEPARATOR_S,
1593                                            item->path, NULL);
1594                 else
1595                         path = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S,
1596                                            folder_path, NULL);
1597         }
1598         g_free(folder_path);
1599
1600         return path;
1601 }
1602
1603 static FolderItem *imap_create_folder(Folder *folder, FolderItem *parent,
1604                                const gchar *name)
1605 {
1606         gchar *dirpath, *imap_path;
1607         IMAPSession *session;
1608         FolderItem *new_item;
1609         gchar separator;
1610         gchar *new_name;
1611         const gchar *p;
1612         gint ok;
1613
1614         g_return_val_if_fail(folder != NULL, NULL);
1615         g_return_val_if_fail(folder->account != NULL, NULL);
1616         g_return_val_if_fail(parent != NULL, NULL);
1617         g_return_val_if_fail(name != NULL, NULL);
1618
1619         session = imap_session_get(folder);
1620         if (!session) return NULL;
1621
1622         if (!folder_item_parent(parent) && strcmp(name, "INBOX") == 0)
1623                 dirpath = g_strdup(name);
1624         else if (parent->path)
1625                 dirpath = g_strconcat(parent->path, "/", name, NULL);
1626         else if ((p = strchr(name, '/')) != NULL && *(p + 1) != '\0')
1627                 dirpath = g_strdup(name);
1628         else if (folder->account->imap_dir && *folder->account->imap_dir) {
1629                 gchar *imap_dir;
1630
1631                 Xstrdup_a(imap_dir, folder->account->imap_dir, return NULL);
1632                 strtailchomp(imap_dir, '/');
1633                 dirpath = g_strconcat(imap_dir, "/", name, NULL);
1634         } else
1635                 dirpath = g_strdup(name);
1636
1637         /* keep trailing directory separator to create a folder that contains
1638            sub folder */
1639         imap_path = imap_locale_to_modified_utf7(dirpath);
1640         strtailchomp(dirpath, '/');
1641         Xstrdup_a(new_name, name, {g_free(dirpath); return NULL;});
1642         strtailchomp(new_name, '/');
1643         separator = imap_get_path_separator(IMAP_FOLDER(folder), imap_path);
1644         imap_path_separator_subst(imap_path, separator);
1645         subst_char(new_name, '/', separator);
1646
1647         if (strcmp(name, "INBOX") != 0) {
1648                 GPtrArray *argbuf;
1649                 gint i;
1650                 gboolean exist = FALSE;
1651
1652                 argbuf = g_ptr_array_new();
1653                 ok = imap_cmd_list(session, NULL, imap_path,
1654                                    argbuf);
1655                 if (ok != IMAP_SUCCESS) {
1656                         log_warning(_("can't create mailbox: LIST failed\n"));
1657                         g_free(imap_path);
1658                         g_free(dirpath);
1659                         ptr_array_free_strings(argbuf);
1660                         g_ptr_array_free(argbuf, TRUE);
1661                         return NULL;
1662                 }
1663
1664                 for (i = 0; i < argbuf->len; i++) {
1665                         gchar *str;
1666                         str = g_ptr_array_index(argbuf, i);
1667                         if (!strncmp(str, "LIST ", 5)) {
1668                                 exist = TRUE;
1669                                 break;
1670                         }
1671                 }
1672                 ptr_array_free_strings(argbuf);
1673                 g_ptr_array_free(argbuf, TRUE);
1674
1675                 if (!exist) {
1676                         ok = imap_cmd_create(session, imap_path);
1677                         if (ok != IMAP_SUCCESS) {
1678                                 log_warning(_("can't create mailbox\n"));
1679                                 g_free(imap_path);
1680                                 g_free(dirpath);
1681                                 return NULL;
1682                         }
1683                 }
1684         }
1685
1686         new_item = folder_item_new(folder, new_name, dirpath);
1687         folder_item_append(parent, new_item);
1688         g_free(imap_path);
1689         g_free(dirpath);
1690
1691         dirpath = folder_item_get_path(new_item);
1692         if (!is_dir_exist(dirpath))
1693                 make_dir_hier(dirpath);
1694         g_free(dirpath);
1695
1696         return new_item;
1697 }
1698
1699 static gint imap_rename_folder(Folder *folder, FolderItem *item,
1700                                const gchar *name)
1701 {
1702         gchar *dirpath;
1703         gchar *newpath;
1704         gchar *real_oldpath;
1705         gchar *real_newpath;
1706         gchar *paths[2];
1707         gchar *old_cache_dir;
1708         gchar *new_cache_dir;
1709         IMAPSession *session;
1710         gchar separator;
1711         gint ok;
1712         gint exists, recent, unseen;
1713         guint32 uid_validity;
1714
1715         g_return_val_if_fail(folder != NULL, -1);
1716         g_return_val_if_fail(item != NULL, -1);
1717         g_return_val_if_fail(item->path != NULL, -1);
1718         g_return_val_if_fail(name != NULL, -1);
1719
1720         if (strchr(name, imap_get_path_separator(IMAP_FOLDER(folder), item->path)) != NULL) {
1721                 g_warning(_("New folder name must not contain the namespace "
1722                             "path separator"));
1723                 return -1;
1724         }
1725
1726         session = imap_session_get(folder);
1727         if (!session) return -1;
1728
1729         real_oldpath = imap_get_real_path(IMAP_FOLDER(folder), item->path);
1730
1731         g_free(session->mbox);
1732         session->mbox = NULL;
1733         ok = imap_cmd_examine(session, "INBOX",
1734                               &exists, &recent, &unseen, &uid_validity);
1735         if (ok != IMAP_SUCCESS) {
1736                 g_free(real_oldpath);
1737                 return -1;
1738         }
1739
1740         separator = imap_get_path_separator(IMAP_FOLDER(folder), item->path);
1741         if (strchr(item->path, G_DIR_SEPARATOR)) {
1742                 dirpath = g_dirname(item->path);
1743                 newpath = g_strconcat(dirpath, G_DIR_SEPARATOR_S, name, NULL);
1744                 g_free(dirpath);
1745         } else
1746                 newpath = g_strdup(name);
1747
1748         real_newpath = imap_locale_to_modified_utf7(newpath);
1749         imap_path_separator_subst(real_newpath, separator);
1750
1751         ok = imap_cmd_rename(session, real_oldpath, real_newpath);
1752         if (ok != IMAP_SUCCESS) {
1753                 log_warning(_("can't rename mailbox: %s to %s\n"),
1754                             real_oldpath, real_newpath);
1755                 g_free(real_oldpath);
1756                 g_free(newpath);
1757                 g_free(real_newpath);
1758                 return -1;
1759         }
1760
1761         g_free(item->name);
1762         item->name = g_strdup(name);
1763
1764         old_cache_dir = folder_item_get_path(item);
1765
1766         paths[0] = g_strdup(item->path);
1767         paths[1] = newpath;
1768         g_node_traverse(item->node, G_PRE_ORDER, G_TRAVERSE_ALL, -1,
1769                         imap_rename_folder_func, paths);
1770
1771         if (is_dir_exist(old_cache_dir)) {
1772                 new_cache_dir = folder_item_get_path(item);
1773                 if (rename(old_cache_dir, new_cache_dir) < 0) {
1774                         FILE_OP_ERROR(old_cache_dir, "rename");
1775                 }
1776                 g_free(new_cache_dir);
1777         }
1778
1779         g_free(old_cache_dir);
1780         g_free(paths[0]);
1781         g_free(newpath);
1782         g_free(real_oldpath);
1783         g_free(real_newpath);
1784
1785         return 0;
1786 }
1787
1788 static gint imap_remove_folder(Folder *folder, FolderItem *item)
1789 {
1790         gint ok;
1791         IMAPSession *session;
1792         gchar *path;
1793         gchar *cache_dir;
1794         gint exists, recent, unseen;
1795         guint32 uid_validity;
1796
1797         g_return_val_if_fail(folder != NULL, -1);
1798         g_return_val_if_fail(item != NULL, -1);
1799         g_return_val_if_fail(item->path != NULL, -1);
1800
1801         session = imap_session_get(folder);
1802         if (!session) return -1;
1803
1804         path = imap_get_real_path(IMAP_FOLDER(folder), item->path);
1805
1806         ok = imap_cmd_examine(session, "INBOX",
1807                               &exists, &recent, &unseen, &uid_validity);
1808         if (ok != IMAP_SUCCESS) {
1809                 g_free(path);
1810                 return -1;
1811         }
1812
1813         ok = imap_cmd_delete(session, path);
1814         if (ok != IMAP_SUCCESS) {
1815                 log_warning(_("can't delete mailbox\n"));
1816                 g_free(path);
1817                 return -1;
1818         }
1819
1820         g_free(path);
1821         cache_dir = folder_item_get_path(item);
1822         if (is_dir_exist(cache_dir) && remove_dir_recursive(cache_dir) < 0)
1823                 g_warning("can't remove directory '%s'\n", cache_dir);
1824         g_free(cache_dir);
1825         folder_item_remove(item);
1826
1827         return 0;
1828 }
1829
1830 static GSList *imap_get_uncached_messages(IMAPSession *session,
1831                                           FolderItem *item,
1832                                           MsgNumberList *numlist)
1833 {
1834         gchar *tmp;
1835         GSList *newlist = NULL;
1836         GSList *llast = NULL;
1837         GString *str;
1838         MsgInfo *msginfo;
1839         GSList *seq_list, *cur;
1840         IMAPSet imapset;
1841
1842         g_return_val_if_fail(session != NULL, NULL);
1843         g_return_val_if_fail(item != NULL, NULL);
1844         g_return_val_if_fail(item->folder != NULL, NULL);
1845         g_return_val_if_fail(FOLDER_CLASS(item->folder) == &imap_class, NULL);
1846
1847         seq_list = imap_get_seq_set_from_numlist(numlist);
1848         for (cur = seq_list; cur != NULL; cur = g_slist_next(cur)) {
1849                 imapset = cur->data;
1850
1851                 if (imap_cmd_envelope(session, imapset)
1852                     != IMAP_SUCCESS) {
1853                         log_warning(_("can't get envelope\n"));
1854                         continue;
1855                 }
1856
1857                 str = g_string_new(NULL);
1858
1859                 for (;;) {
1860                         if ((tmp = sock_getline(SESSION(session)->sock)) == NULL) {
1861                                 log_warning(_("error occurred while getting envelope.\n"));
1862                                 g_string_free(str, TRUE);
1863                                 break;
1864                         }
1865                         strretchomp(tmp);
1866                         if (tmp[0] != '*' || tmp[1] != ' ') {
1867                                 log_print("IMAP4< %s\n", tmp);
1868                                 g_free(tmp);
1869                                 break;
1870                         }
1871                         if (strstr(tmp, "FETCH") == NULL) {
1872                                 log_print("IMAP4< %s\n", tmp);
1873                                 g_free(tmp);
1874                                 continue;
1875                         }
1876                         log_print("IMAP4< %s\n", tmp);
1877                         g_string_assign(str, tmp);
1878                         g_free(tmp);
1879
1880                         msginfo = imap_parse_envelope
1881                                 (SESSION(session)->sock, item, str);
1882                         if (!msginfo) {
1883                                 log_warning(_("can't parse envelope: %s\n"), str->str);
1884                                 continue;
1885                         }
1886                         if (item->stype == F_QUEUE) {
1887                                 MSG_SET_TMP_FLAGS(msginfo->flags, MSG_QUEUED);
1888                         } else if (item->stype == F_DRAFT) {
1889                                 MSG_SET_TMP_FLAGS(msginfo->flags, MSG_DRAFT);
1890                         }
1891
1892                         msginfo->folder = item;
1893
1894                         if (!newlist)
1895                                 llast = newlist = g_slist_append(newlist, msginfo);
1896                         else {
1897                                 llast = g_slist_append(llast, msginfo);
1898                                 llast = llast->next;
1899                         }
1900                 }
1901
1902                 g_string_free(str, TRUE);
1903         }
1904         imap_seq_set_free(seq_list);
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         return IMAP_SUCCESS;
3266 }
3267
3268
3269 /* misc utility functions */
3270
3271 static gchar *strchr_cpy(const gchar *src, gchar ch, gchar *dest, gint len)
3272 {
3273         gchar *tmp;
3274
3275         dest[0] = '\0';
3276         tmp = strchr(src, ch);
3277         if (!tmp)
3278                 return NULL;
3279
3280         memcpy(dest, src, MIN(tmp - src, len - 1));
3281         dest[MIN(tmp - src, len - 1)] = '\0';
3282
3283         return tmp + 1;
3284 }
3285
3286 static gchar *get_quoted(const gchar *src, gchar ch, gchar *dest, gint len)
3287 {
3288         const gchar *p = src;
3289         gint n = 0;
3290
3291         g_return_val_if_fail(*p == ch, NULL);
3292
3293         *dest = '\0';
3294         p++;
3295
3296         while (*p != '\0' && *p != ch) {
3297                 if (n < len - 1) {
3298                         if (*p == '\\' && *(p + 1) != '\0')
3299                                 p++;
3300                         *dest++ = *p++;
3301                 } else
3302                         p++;
3303                 n++;
3304         }
3305
3306         *dest = '\0';
3307         return (gchar *)(*p == ch ? p + 1 : p);
3308 }
3309
3310 static gchar *search_array_contain_str(GPtrArray *array, const gchar *str)
3311 {
3312         gint i;
3313
3314         for (i = 0; i < array->len; i++) {
3315                 gchar *tmp;
3316
3317                 tmp = g_ptr_array_index(array, i);
3318                 if (strstr(tmp, str) != NULL)
3319                         return tmp;
3320         }
3321
3322         return NULL;
3323 }
3324
3325 static gchar *search_array_str(GPtrArray *array, const gchar *str)
3326 {
3327         gint i;
3328         gint len;
3329
3330         len = strlen(str);
3331
3332         for (i = 0; i < array->len; i++) {
3333                 gchar *tmp;
3334
3335                 tmp = g_ptr_array_index(array, i);
3336                 if (!strncmp(tmp, str, len))
3337                         return tmp;
3338         }
3339
3340         return NULL;
3341 }
3342
3343 static void imap_path_separator_subst(gchar *str, gchar separator)
3344 {
3345         gchar *p;
3346         gboolean in_escape = FALSE;
3347
3348         if (!separator || separator == '/') return;
3349
3350         for (p = str; *p != '\0'; p++) {
3351                 if (*p == '/' && !in_escape)
3352                         *p = separator;
3353                 else if (*p == '&' && *(p + 1) != '-' && !in_escape)
3354                         in_escape = TRUE;
3355                 else if (*p == '-' && in_escape)
3356                         in_escape = FALSE;
3357         }
3358 }
3359
3360 static gchar *imap_modified_utf7_to_locale(const gchar *mutf7_str)
3361 {
3362 #if !HAVE_ICONV
3363         const gchar *from_p;
3364         gchar *to, *to_p;
3365
3366         to = g_malloc(strlen(mutf7_str) + 1);
3367         to_p = to;
3368
3369         for (from_p = mutf7_str; *from_p != '\0'; from_p++) {
3370                 if (*from_p == '&' && *(from_p + 1) == '-') {
3371                         *to_p++ = '&';
3372                         from_p++;
3373                 } else
3374                         *to_p++ = *from_p;
3375         }
3376         *to_p = '\0';
3377
3378         return to;
3379 #else
3380         static iconv_t cd = (iconv_t)-1;
3381         static gboolean iconv_ok = TRUE;
3382         GString *norm_utf7;
3383         gchar *norm_utf7_p;
3384         size_t norm_utf7_len;
3385         const gchar *p;
3386         gchar *to_str, *to_p;
3387         size_t to_len;
3388         gboolean in_escape = FALSE;
3389
3390         if (!iconv_ok) return g_strdup(mutf7_str);
3391
3392         if (cd == (iconv_t)-1) {
3393                 cd = iconv_open(conv_get_current_charset_str(), "UTF-7");
3394                 if (cd == (iconv_t)-1) {
3395                         g_warning("iconv cannot convert UTF-7 to %s\n",
3396                                   conv_get_current_charset_str());
3397                         iconv_ok = FALSE;
3398                         return g_strdup(mutf7_str);
3399                 }
3400         }
3401
3402         norm_utf7 = g_string_new(NULL);
3403
3404         for (p = mutf7_str; *p != '\0'; p++) {
3405                 /* replace: '&'  -> '+',
3406                             "&-" -> '&',
3407                             escaped ','  -> '/' */
3408                 if (!in_escape && *p == '&') {
3409                         if (*(p + 1) != '-') {
3410                                 g_string_append_c(norm_utf7, '+');
3411                                 in_escape = TRUE;
3412                         } else {
3413                                 g_string_append_c(norm_utf7, '&');
3414                                 p++;
3415                         }
3416                 } else if (in_escape && *p == ',') {
3417                         g_string_append_c(norm_utf7, '/');
3418                 } else if (in_escape && *p == '-') {
3419                         g_string_append_c(norm_utf7, '-');
3420                         in_escape = FALSE;
3421                 } else {
3422                         g_string_append_c(norm_utf7, *p);
3423                 }
3424         }
3425
3426         norm_utf7_p = norm_utf7->str;
3427         norm_utf7_len = norm_utf7->len;
3428         to_len = strlen(mutf7_str) * 5;
3429         to_p = to_str = g_malloc(to_len + 1);
3430
3431         if (iconv(cd, (ICONV_CONST gchar **)&norm_utf7_p, &norm_utf7_len,
3432                   &to_p, &to_len) == -1) {
3433                 g_warning(_("iconv cannot convert UTF-7 to %s\n"),
3434                           conv_get_current_charset_str());
3435                 g_string_free(norm_utf7, TRUE);
3436                 g_free(to_str);
3437                 return g_strdup(mutf7_str);
3438         }
3439
3440         /* second iconv() call for flushing */
3441         iconv(cd, NULL, NULL, &to_p, &to_len);
3442         g_string_free(norm_utf7, TRUE);
3443         *to_p = '\0';
3444
3445         return to_str;
3446 #endif /* !HAVE_ICONV */
3447 }
3448
3449 static gchar *imap_locale_to_modified_utf7(const gchar *from)
3450 {
3451 #if !HAVE_ICONV
3452         const gchar *from_p;
3453         gchar *to, *to_p;
3454
3455         to = g_malloc(strlen(from) * 2 + 1);
3456         to_p = to;
3457
3458         for (from_p = from; *from_p != '\0'; from_p++) {
3459                 if (*from_p == '&') {
3460                         *to_p++ = '&';
3461                         *to_p++ = '-';
3462                 } else
3463                         *to_p++ = *from_p;
3464         }
3465         *to_p = '\0';
3466
3467         return to;
3468 #else
3469         static iconv_t cd = (iconv_t)-1;
3470         static gboolean iconv_ok = TRUE;
3471         gchar *norm_utf7, *norm_utf7_p;
3472         size_t from_len, norm_utf7_len;
3473         GString *to_str;
3474         gchar *from_tmp, *to, *p;
3475         gboolean in_escape = FALSE;
3476
3477         if (!iconv_ok) return g_strdup(from);
3478
3479         if (cd == (iconv_t)-1) {
3480                 cd = iconv_open("UTF-7", conv_get_current_charset_str());
3481                 if (cd == (iconv_t)-1) {
3482                         g_warning("iconv cannot convert %s to UTF-7\n",
3483                                   conv_get_current_charset_str());
3484                         iconv_ok = FALSE;
3485                         return g_strdup(from);
3486                 }
3487         }
3488
3489         Xstrdup_a(from_tmp, from, return g_strdup(from));
3490         from_len = strlen(from);
3491         norm_utf7_len = from_len * 5;
3492         Xalloca(norm_utf7, norm_utf7_len + 1, return g_strdup(from));
3493         norm_utf7_p = norm_utf7;
3494
3495 #define IS_PRINT(ch) (isprint(ch) && isascii(ch))
3496
3497         while (from_len > 0) {
3498                 if (*from_tmp == '+') {
3499                         *norm_utf7_p++ = '+';
3500                         *norm_utf7_p++ = '-';
3501                         norm_utf7_len -= 2;
3502                         from_tmp++;
3503                         from_len--;
3504                 } else if (IS_PRINT(*(guchar *)from_tmp)) {
3505                         /* printable ascii char */
3506                         *norm_utf7_p = *from_tmp;
3507                         norm_utf7_p++;
3508                         norm_utf7_len--;
3509                         from_tmp++;
3510                         from_len--;
3511                 } else {
3512                         size_t mb_len = 0, conv_len = 0;
3513
3514                         /* unprintable char: convert to UTF-7 */
3515                         p = from_tmp;
3516                         while (!IS_PRINT(*(guchar *)p) && conv_len < from_len) {
3517                                 mb_len = mblen(p, MB_LEN_MAX);
3518                                 if (mb_len <= 0) {
3519                                         g_warning("wrong multibyte sequence\n");
3520                                         return g_strdup(from);
3521                                 }
3522                                 conv_len += mb_len;
3523                                 p += mb_len;
3524                         }
3525
3526                         from_len -= conv_len;
3527                         if (iconv(cd, (ICONV_CONST gchar **)&from_tmp,
3528                                   &conv_len,
3529                                   &norm_utf7_p, &norm_utf7_len) == -1) {
3530                                 g_warning("iconv cannot convert %s to UTF-7\n",
3531                                           conv_get_current_charset_str());
3532                                 return g_strdup(from);
3533                         }
3534
3535                         /* second iconv() call for flushing */
3536                         iconv(cd, NULL, NULL, &norm_utf7_p, &norm_utf7_len);
3537                 }
3538         }
3539
3540 #undef IS_PRINT
3541
3542         *norm_utf7_p = '\0';
3543         to_str = g_string_new(NULL);
3544         for (p = norm_utf7; p < norm_utf7_p; p++) {
3545                 /* replace: '&' -> "&-",
3546                             '+' -> '&',
3547                             "+-" -> '+',
3548                             BASE64 '/' -> ',' */
3549                 if (!in_escape && *p == '&') {
3550                         g_string_append(to_str, "&-");
3551                 } else if (!in_escape && *p == '+') {
3552                         if (*(p + 1) == '-') {
3553                                 g_string_append_c(to_str, '+');
3554                                 p++;
3555                         } else {
3556                                 g_string_append_c(to_str, '&');
3557                                 in_escape = TRUE;
3558                         }
3559                 } else if (in_escape && *p == '/') {
3560                         g_string_append_c(to_str, ',');
3561                 } else if (in_escape && *p == '-') {
3562                         g_string_append_c(to_str, '-');
3563                         in_escape = FALSE;
3564                 } else {
3565                         g_string_append_c(to_str, *p);
3566                 }
3567         }
3568
3569         if (in_escape) {
3570                 in_escape = FALSE;
3571                 g_string_append_c(to_str, '-');
3572         }
3573
3574         to = to_str->str;
3575         g_string_free(to_str, FALSE);
3576
3577         return to;
3578 #endif /* !HAVE_ICONV */
3579 }
3580
3581 static GSList *imap_get_seq_set_from_numlist(MsgNumberList *numlist)
3582 {
3583         GString *str;
3584         GSList *sorted_list, *cur;
3585         guint first, last, next;
3586         gchar *ret_str;
3587         GSList *ret_list = NULL;
3588
3589         if (numlist == NULL)
3590                 return NULL;
3591
3592         str = g_string_sized_new(256);
3593
3594         sorted_list = g_slist_copy(numlist);
3595         sorted_list = g_slist_sort(sorted_list, g_int_compare);
3596
3597         first = GPOINTER_TO_INT(sorted_list->data);
3598
3599         for (cur = sorted_list; cur != NULL; cur = g_slist_next(cur)) {
3600                 last = GPOINTER_TO_INT(cur->data);
3601                 if (cur->next)
3602                         next = GPOINTER_TO_INT(cur->next->data);
3603                 else
3604                         next = 0;
3605
3606                 if (last + 1 != next || next == 0) {
3607                         if (str->len > 0)
3608                                 g_string_append_c(str, ',');
3609                         if (first == last)
3610                                 g_string_sprintfa(str, "%u", first);
3611                         else
3612                                 g_string_sprintfa(str, "%u:%u", first, last);
3613
3614                         first = next;
3615
3616                         if (str->len > IMAP_CMD_LIMIT) {
3617                                 ret_str = g_strdup(str->str);
3618                                 ret_list = g_slist_append(ret_list, ret_str);
3619                                 g_string_truncate(str, 0);
3620                         }
3621                 }
3622         }
3623
3624         if (str->len > 0) {
3625                 ret_str = g_strdup(str->str);
3626                 ret_list = g_slist_append(ret_list, ret_str);
3627         }
3628
3629         g_slist_free(sorted_list);
3630         g_string_free(str, TRUE);
3631
3632         return ret_list;
3633 }
3634
3635 static GSList *imap_get_seq_set_from_msglist(MsgInfoList *msglist)
3636 {
3637         MsgNumberList *numlist = NULL;
3638         MsgInfoList *cur;
3639         GSList *seq_list;
3640
3641         for (cur = msglist; cur != NULL; cur = g_slist_next(cur)) {
3642                 MsgInfo *msginfo = (MsgInfo *) cur->data;
3643
3644                 numlist = g_slist_append(numlist, GINT_TO_POINTER(msginfo->msgnum));
3645         }
3646         seq_list = imap_get_seq_set_from_numlist(numlist);
3647         g_slist_free(numlist);
3648
3649         return seq_list;
3650 }
3651
3652 static void imap_seq_set_free(GSList *seq_list)
3653 {
3654         slist_free_strings(seq_list);
3655         g_slist_free(seq_list);
3656 }
3657
3658
3659 static gboolean imap_rename_folder_func(GNode *node, gpointer data)
3660 {
3661         FolderItem *item = node->data;
3662         gchar **paths = data;
3663         const gchar *oldpath = paths[0];
3664         const gchar *newpath = paths[1];
3665         gchar *base;
3666         gchar *new_itempath;
3667         gint oldpathlen;
3668
3669         oldpathlen = strlen(oldpath);
3670         if (strncmp(oldpath, item->path, oldpathlen) != 0) {
3671                 g_warning("path doesn't match: %s, %s\n", oldpath, item->path);
3672                 return TRUE;
3673         }
3674
3675         base = item->path + oldpathlen;
3676         while (*base == G_DIR_SEPARATOR) base++;
3677         if (*base == '\0')
3678                 new_itempath = g_strdup(newpath);
3679         else
3680                 new_itempath = g_strconcat(newpath, G_DIR_SEPARATOR_S, base,
3681                                            NULL);
3682         g_free(item->path);
3683         item->path = new_itempath;
3684
3685         return FALSE;
3686 }
3687
3688 static gint get_list_of_uids(Folder *folder, IMAPFolderItem *item, GSList **msgnum_list)
3689 {
3690         gint ok, nummsgs = 0, lastuid_old;
3691         IMAPSession *session;
3692         GSList *uidlist, *elem;
3693         gchar *cmd_buf;
3694
3695         session = imap_session_get(folder);
3696         g_return_val_if_fail(session != NULL, -1);
3697
3698         ok = imap_select(session, IMAP_FOLDER(folder), item->item.path,
3699                          NULL, NULL, NULL, NULL);
3700         if (ok != IMAP_SUCCESS)
3701                 return -1;
3702
3703         cmd_buf = g_strdup_printf("UID %d:*", item->lastuid + 1);
3704         ok = imap_cmd_search(session, cmd_buf, &uidlist);
3705         g_free(cmd_buf);
3706
3707         if (ok == IMAP_SOCKET) {
3708                 session_destroy((Session *)session);
3709                 ((RemoteFolder *)folder)->session = NULL;
3710                 return -1;
3711         }
3712
3713         if (ok != IMAP_SUCCESS) {
3714                 gint i;
3715                 GPtrArray *argbuf;
3716
3717                 argbuf = g_ptr_array_new();
3718
3719                 cmd_buf = g_strdup_printf("UID FETCH %d:* (UID)", item->lastuid + 1);
3720                 imap_gen_send(session, cmd_buf);
3721                 g_free(cmd_buf);
3722                 ok = imap_cmd_ok(session, argbuf);
3723                 if (ok != IMAP_SUCCESS) {
3724                         ptr_array_free_strings(argbuf);
3725                         g_ptr_array_free(argbuf, TRUE);
3726                         return -1;
3727                 }
3728         
3729                 for(i = 0; i < argbuf->len; i++) {
3730                         int ret, msgnum;
3731         
3732                         if((ret = sscanf(g_ptr_array_index(argbuf, i), 
3733                                     "%*d FETCH (UID %d)", &msgnum)) == 1)
3734                                 uidlist = g_slist_prepend(uidlist, GINT_TO_POINTER(msgnum));
3735                 }
3736                 ptr_array_free_strings(argbuf);
3737                 g_ptr_array_free(argbuf, TRUE);
3738         }
3739
3740         lastuid_old = item->lastuid;
3741         *msgnum_list = g_slist_copy(item->uid_list);
3742         nummsgs = g_slist_length(*msgnum_list);
3743         debug_print("Got %d uids from cache\n", g_slist_length(item->uid_list));
3744
3745         for (elem = uidlist; elem != NULL; elem = g_slist_next(elem)) {
3746                 guint msgnum;
3747
3748                 msgnum = GPOINTER_TO_INT(elem->data);
3749                 if (msgnum > lastuid_old) {
3750                         *msgnum_list = g_slist_prepend(*msgnum_list, GINT_TO_POINTER(msgnum));
3751                         item->uid_list = g_slist_prepend(item->uid_list, GINT_TO_POINTER(msgnum));
3752                         nummsgs++;
3753
3754                         if(msgnum > item->lastuid)
3755                                 item->lastuid = msgnum;
3756                 }
3757         }
3758         g_slist_free(uidlist);
3759
3760         return nummsgs;
3761 }
3762
3763 gint imap_get_num_list(Folder *folder, FolderItem *_item, GSList **msgnum_list, gboolean *old_uids_valid)
3764 {
3765         IMAPFolderItem *item = (IMAPFolderItem *)_item;
3766         IMAPSession *session;
3767         gint ok, nummsgs = 0, exists, recent, uid_val, uid_next, unseen;
3768         GSList *uidlist;
3769         gchar *dir;
3770         gboolean selected_folder;
3771
3772         g_return_val_if_fail(folder != NULL, -1);
3773         g_return_val_if_fail(item != NULL, -1);
3774         g_return_val_if_fail(item->item.path != NULL, -1);
3775         g_return_val_if_fail(FOLDER_CLASS(folder) == &imap_class, -1);
3776         g_return_val_if_fail(folder->account != NULL, -1);
3777
3778         session = imap_session_get(folder);
3779         g_return_val_if_fail(session != NULL, -1);
3780
3781         selected_folder = (session->mbox != NULL) &&
3782                           (!strcmp(session->mbox, item->item.path));
3783         if (selected_folder) {
3784                 ok = imap_cmd_noop(session);
3785                 if (ok != IMAP_SUCCESS)
3786                         return -1;
3787                 exists = session->exists;
3788
3789                 *old_uids_valid = TRUE;
3790         } else {
3791                 ok = imap_status(session, IMAP_FOLDER(folder), item->item.path,
3792                                  &exists, &recent, &uid_next, &uid_val, &unseen);
3793                 if (ok != IMAP_SUCCESS)
3794                         return -1;
3795
3796                 if(item->item.mtime == uid_val)
3797                         *old_uids_valid = TRUE;
3798                 else {
3799                         *old_uids_valid = FALSE;
3800
3801                         debug_print("Freeing imap uid cache\n");
3802                         item->lastuid = 0;
3803                         g_slist_free(item->uid_list);
3804                         item->uid_list = NULL;
3805                 
3806                         item->item.mtime = uid_val;
3807
3808                         imap_delete_all_cached_messages((FolderItem *)item);
3809                 }
3810         }
3811
3812         if (!selected_folder)
3813                 item->uid_next = uid_next;
3814
3815         /* If old uid_next matches new uid_next we can be sure no message
3816            was added to the folder */
3817         if (( selected_folder && !session->folder_content_changed) ||
3818             (!selected_folder && uid_next == item->uid_next)) {
3819                 nummsgs = g_slist_length(item->uid_list);
3820
3821                 /* If number of messages is still the same we
3822                    know our caches message numbers are still valid,
3823                    otherwise if the number of messages has decrease
3824                    we discard our cache to start a new scan to find
3825                    out which numbers have been removed */
3826                 if (exists == nummsgs) {
3827                         *msgnum_list = g_slist_copy(item->uid_list);
3828                         return nummsgs;
3829                 } else if (exists < nummsgs) {
3830                         debug_print("Freeing imap uid cache");
3831                         item->lastuid = 0;
3832                         g_slist_free(item->uid_list);
3833                         item->uid_list = NULL;
3834                 }
3835         }
3836
3837         if (exists == 0) {
3838                 *msgnum_list = NULL;
3839                 return 0;
3840         }
3841
3842         nummsgs = get_list_of_uids(folder, item, &uidlist);
3843
3844         if (nummsgs != exists) {
3845                 /* Cache contains more messages then folder, we have cached
3846                    an old UID of a message that was removed and new messages
3847                    have been added too, otherwise the uid_next check would
3848                    not have failed */
3849                 debug_print("Freeing imap uid cache");
3850                 item->lastuid = 0;
3851                 g_slist_free(item->uid_list);
3852                 item->uid_list = NULL;
3853
3854                 g_slist_free(*msgnum_list);
3855
3856                 nummsgs = get_list_of_uids(folder, item, &uidlist);
3857         }
3858
3859         *msgnum_list = uidlist;
3860
3861         dir = folder_item_get_path((FolderItem *)item);
3862         debug_print("removing old messages from %s\n", dir);
3863         remove_numbered_files_not_in_list(dir, *msgnum_list);
3864         g_free(dir);
3865
3866         return nummsgs;
3867 }
3868
3869 static MsgInfo *imap_parse_msg(const gchar *file, FolderItem *item)
3870 {
3871         MsgInfo *msginfo;
3872         MsgFlags flags;
3873
3874         flags.perm_flags = MSG_NEW|MSG_UNREAD;
3875         flags.tmp_flags = 0;
3876
3877         g_return_val_if_fail(item != NULL, NULL);
3878         g_return_val_if_fail(file != NULL, NULL);
3879
3880         if (item->stype == F_QUEUE) {
3881                 MSG_SET_TMP_FLAGS(flags, MSG_QUEUED);
3882         } else if (item->stype == F_DRAFT) {
3883                 MSG_SET_TMP_FLAGS(flags, MSG_DRAFT);
3884         }
3885
3886         msginfo = procheader_parse_file(file, flags, FALSE, FALSE);
3887         if (!msginfo) return NULL;
3888
3889         msginfo->folder = item;
3890
3891         return msginfo;
3892 }
3893
3894 GSList *imap_get_msginfos(Folder *folder, FolderItem *item, GSList *msgnum_list)
3895 {
3896         IMAPSession *session;
3897         MsgInfoList *ret = NULL;
3898         gint ok;
3899
3900         g_return_val_if_fail(folder != NULL, NULL);
3901         g_return_val_if_fail(item != NULL, NULL);
3902         g_return_val_if_fail(msgnum_list != NULL, NULL);
3903
3904         session = imap_session_get(folder);
3905         g_return_val_if_fail(session != NULL, NULL);
3906
3907         ok = imap_select(session, IMAP_FOLDER(folder), item->path,
3908                          NULL, NULL, NULL, NULL);
3909         if (ok != IMAP_SUCCESS)
3910                 return NULL;
3911
3912         if (!(item->stype == F_QUEUE || item->stype == F_DRAFT)) {
3913                 ret = g_slist_concat(ret,
3914                         imap_get_uncached_messages(
3915                         session, item, msgnum_list));
3916         } else {
3917                 MsgNumberList *sorted_list, *elem;
3918                 gint startnum, lastnum;
3919
3920                 sorted_list = g_slist_sort(g_slist_copy(msgnum_list), g_int_compare);
3921
3922                 startnum = lastnum = GPOINTER_TO_INT(sorted_list->data);
3923
3924                 for (elem = sorted_list;; elem = g_slist_next(elem)) {
3925                         guint num;
3926
3927                         if (elem)
3928                                 num = GPOINTER_TO_INT(elem->data);
3929
3930                         if (num > lastnum + 1 || elem == NULL) {
3931                                 int i;
3932                                 for (i = startnum; i <= lastnum; ++i) {
3933                                         gchar *file;
3934                         
3935                                         file = imap_fetch_msg(folder, item, i);
3936                                         if (file != NULL) {
3937                                                 MsgInfo *msginfo = imap_parse_msg(file, item);
3938                                                 if (msginfo != NULL) {
3939                                                         msginfo->msgnum = i;
3940                                                         ret = g_slist_append(ret, msginfo);
3941                                                 }
3942                                                 g_free(file);
3943                                         }
3944                                 }
3945
3946                                 if (elem == NULL)
3947                                         break;
3948
3949                                 startnum = num;
3950                         }
3951                         lastnum = num;
3952                 }
3953
3954                 g_slist_free(sorted_list);
3955         }
3956
3957         return ret;
3958 }
3959
3960 MsgInfo *imap_get_msginfo(Folder *folder, FolderItem *item, gint uid)
3961 {
3962         MsgInfo *msginfo = NULL;
3963         MsgInfoList *msginfolist;
3964         MsgNumberList numlist;
3965
3966         numlist.next = NULL;
3967         numlist.data = GINT_TO_POINTER(uid);
3968
3969         msginfolist = imap_get_msginfos(folder, item, &numlist);
3970         if (msginfolist != NULL) {
3971                 msginfo = msginfolist->data;
3972                 g_slist_free(msginfolist);
3973         }
3974
3975         return msginfo;
3976 }
3977
3978 gboolean imap_scan_required(Folder *folder, FolderItem *_item)
3979 {
3980         IMAPSession *session;
3981         IMAPFolderItem *item = (IMAPFolderItem *)_item;
3982         gint ok, exists = 0, recent = 0, unseen = 0;
3983         guint32 uid_next, uid_val = 0;
3984         gboolean selected_folder;
3985         
3986         g_return_val_if_fail(folder != NULL, FALSE);
3987         g_return_val_if_fail(item != NULL, FALSE);
3988         g_return_val_if_fail(item->item.folder != NULL, FALSE);
3989         g_return_val_if_fail(FOLDER_CLASS(item->item.folder) == &imap_class, FALSE);
3990
3991         if (item->item.path == NULL)
3992                 return FALSE;
3993
3994         session = imap_session_get(folder);
3995         g_return_val_if_fail(session != NULL, FALSE);
3996
3997         selected_folder = (session->mbox != NULL) &&
3998                           (!strcmp(session->mbox, item->item.path));
3999         if (selected_folder) {
4000                 ok = imap_cmd_noop(session);
4001                 if (ok != IMAP_SUCCESS)
4002                         return FALSE;
4003
4004                 if (session->folder_content_changed)
4005                         return TRUE;
4006         } else {
4007                 ok = imap_status(session, IMAP_FOLDER(folder), item->item.path,
4008                                  &exists, &recent, &uid_next, &uid_val, &unseen);
4009                 if (ok != IMAP_SUCCESS)
4010                         return FALSE;
4011
4012                 if ((uid_next != item->uid_next) || (exists < item->item.total_msgs))
4013                         return TRUE;
4014         }
4015
4016         return FALSE;
4017 }
4018
4019 void imap_change_flags(Folder *folder, FolderItem *item, MsgInfo *msginfo, MsgPermFlags newflags)
4020 {
4021         IMAPSession *session;
4022         IMAPFlags flags_set = 0, flags_unset = 0;
4023         gint ok = IMAP_SUCCESS;
4024         MsgNumberList numlist;
4025         
4026         g_return_if_fail(folder != NULL);
4027         g_return_if_fail(folder->klass == &imap_class);
4028         g_return_if_fail(item != NULL);
4029         g_return_if_fail(item->folder == folder);
4030         g_return_if_fail(msginfo != NULL);
4031         g_return_if_fail(msginfo->folder == item);
4032
4033         session = imap_session_get(folder);
4034         if (!session) return;
4035
4036         if ((ok = imap_select(session, IMAP_FOLDER(folder), msginfo->folder->path,
4037             NULL, NULL, NULL, NULL)) != IMAP_SUCCESS)
4038                 return;
4039
4040         if (!MSG_IS_MARKED(msginfo->flags) &&  (newflags & MSG_MARKED))
4041                 flags_set |= IMAP_FLAG_FLAGGED;
4042         if ( MSG_IS_MARKED(msginfo->flags) && !(newflags & MSG_MARKED))
4043                 flags_unset |= IMAP_FLAG_FLAGGED;
4044
4045         if (!MSG_IS_UNREAD(msginfo->flags) &&  (newflags & MSG_UNREAD))
4046                 flags_unset |= IMAP_FLAG_SEEN;
4047         if ( MSG_IS_UNREAD(msginfo->flags) && !(newflags & MSG_UNREAD))
4048                 flags_set |= IMAP_FLAG_SEEN;
4049
4050         if (!MSG_IS_REPLIED(msginfo->flags) &&  (newflags & MSG_REPLIED))
4051                 flags_set |= IMAP_FLAG_ANSWERED;
4052         if ( MSG_IS_REPLIED(msginfo->flags) && !(newflags & MSG_REPLIED))
4053                 flags_set |= IMAP_FLAG_ANSWERED;
4054
4055         numlist.next = NULL;
4056         numlist.data = GINT_TO_POINTER(msginfo->msgnum);
4057         
4058         if (flags_set) {
4059                 ok = imap_set_message_flags(session, &numlist, flags_set, TRUE);
4060                 if (ok != IMAP_SUCCESS) return;
4061         }
4062
4063         if (flags_unset) {
4064                 ok = imap_set_message_flags(session, &numlist, flags_unset, FALSE);
4065                 if (ok != IMAP_SUCCESS) return;
4066         }
4067
4068         msginfo->flags.perm_flags = newflags;
4069
4070         return;
4071 }
4072
4073 static gint compare_msginfo(gconstpointer a, gconstpointer b)
4074 {
4075         return ((MsgInfo *)a)->msgnum - ((MsgInfo *)b)->msgnum;
4076 }
4077
4078 static guint gslist_find_next_num(MsgNumberList **list, guint num)
4079 {
4080         GSList *elem;
4081
4082         g_return_val_if_fail(list != NULL, -1);
4083
4084         for (elem = *list; elem != NULL;&n