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