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