Updated for string-utils namespace changes.
[platform/upstream/evolution-data-server.git] / camel / providers / imap / camel-imap-store.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /* camel-imap-store.c : class for an imap store */
3
4 /*
5  *  Authors:
6  *    Dan Winship <danw@ximian.com>
7  *    Jeffrey Stedfast <fejj@ximian.com>
8  *
9  *  Copyright 2000, 2001 Ximian, Inc.
10  *
11  * This program is free software; you can redistribute it and/or
12  * modify it under the terms of version 2 of the GNU General Public
13  * License as published by the Free Software Foundation.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18  * General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public
21  * License along with this program; if not, write to the
22  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
23  * Boston, MA 02111-1307, USA.
24  *
25  */
26
27
28 #ifdef HAVE_CONFIG_H
29 #include <config.h>
30 #endif
31
32 #include <stdio.h>
33 #include <stdlib.h>
34 #include <string.h>
35 #include <unistd.h>
36 #include <errno.h>
37
38 #include "e-util/e-path.h"
39
40 #include "camel-imap-store.h"
41 #include "camel-imap-store-summary.h"
42 #include "camel-imap-folder.h"
43 #include "camel-imap-utils.h"
44 #include "camel-imap-command.h"
45 #include "camel-imap-summary.h"
46 #include "camel-imap-message-cache.h"
47 #include "camel-disco-diary.h"
48 #include "camel-file-utils.h"
49 #include "camel-folder.h"
50 #include "camel-exception.h"
51 #include "camel-session.h"
52 #include "camel-stream.h"
53 #include "camel-stream-buffer.h"
54 #include "camel-stream-fs.h"
55 #include "camel-tcp-stream-raw.h"
56 #include "camel-tcp-stream-ssl.h"
57 #include "camel-url.h"
58 #include "camel-sasl.h"
59 #include "camel-utf8.h"
60 #include "camel-string-utils.h"
61
62 #include "camel-imap-private.h"
63 #include "camel-private.h"
64
65 #define d(x) 
66
67 /* Specified in RFC 2060 */
68 #define IMAP_PORT 143
69 #define SIMAP_PORT 993
70
71
72 extern int camel_verbose_debug;
73
74 static CamelDiscoStoreClass *parent_class = NULL;
75
76 static char imap_tag_prefix = 'A';
77
78 static void construct (CamelService *service, CamelSession *session,
79                        CamelProvider *provider, CamelURL *url,
80                        CamelException *ex);
81
82 static int imap_setv (CamelObject *object, CamelException *ex, CamelArgV *args);
83 static int imap_getv (CamelObject *object, CamelException *ex, CamelArgGetV *args);
84
85 static char *imap_get_name (CamelService *service, gboolean brief);
86
87 static gboolean can_work_offline (CamelDiscoStore *disco_store);
88 static gboolean imap_connect_online (CamelService *service, CamelException *ex);
89 static gboolean imap_connect_offline (CamelService *service, CamelException *ex);
90 static gboolean imap_disconnect_online (CamelService *service, gboolean clean, CamelException *ex);
91 static gboolean imap_disconnect_offline (CamelService *service, gboolean clean, CamelException *ex);
92 static void imap_noop (CamelStore *store, CamelException *ex);
93 static GList *query_auth_types (CamelService *service, CamelException *ex);
94 static guint hash_folder_name (gconstpointer key);
95 static gint compare_folder_name (gconstpointer a, gconstpointer b);
96 static CamelFolder *get_folder_online (CamelStore *store, const char *folder_name, guint32 flags, CamelException *ex);
97 static CamelFolder *get_folder_offline (CamelStore *store, const char *folder_name, guint32 flags, CamelException *ex);
98 static CamelFolderInfo *create_folder (CamelStore *store, const char *parent_name, const char *folder_name, CamelException *ex);
99 static void             delete_folder (CamelStore *store, const char *folder_name, CamelException *ex);
100 static void             rename_folder (CamelStore *store, const char *old_name, const char *new_name, CamelException *ex);
101 static CamelFolderInfo *get_folder_info_online (CamelStore *store,
102                                                 const char *top,
103                                                 guint32 flags,
104                                                 CamelException *ex);
105 static CamelFolderInfo *get_folder_info_offline (CamelStore *store,
106                                                  const char *top,
107                                                  guint32 flags,
108                                                  CamelException *ex);
109 static gboolean folder_subscribed (CamelStore *store, const char *folder_name);
110 static void subscribe_folder (CamelStore *store, const char *folder_name,
111                               CamelException *ex);
112 static void unsubscribe_folder (CamelStore *store, const char *folder_name,
113                                 CamelException *ex);
114
115 static void get_folders_online (CamelImapStore *imap_store, const char *pattern,
116                                 GPtrArray *folders, gboolean lsub, CamelException *ex);
117
118
119 static void imap_folder_effectively_unsubscribed(CamelImapStore *imap_store, const char *folder_name, CamelException *ex);
120 static gboolean imap_check_folder_still_extant (CamelImapStore *imap_store, const char *full_name,  CamelException *ex);
121 static void imap_forget_folder(CamelImapStore *imap_store, const char *folder_name, CamelException *ex);
122 static void imap_set_server_level (CamelImapStore *store);
123
124 static void
125 camel_imap_store_class_init (CamelImapStoreClass *camel_imap_store_class)
126 {
127         CamelObjectClass *camel_object_class =
128                 CAMEL_OBJECT_CLASS (camel_imap_store_class);
129         CamelServiceClass *camel_service_class =
130                 CAMEL_SERVICE_CLASS (camel_imap_store_class);
131         CamelStoreClass *camel_store_class =
132                 CAMEL_STORE_CLASS (camel_imap_store_class);
133         CamelDiscoStoreClass *camel_disco_store_class =
134                 CAMEL_DISCO_STORE_CLASS (camel_imap_store_class);
135         
136         parent_class = CAMEL_DISCO_STORE_CLASS (camel_type_get_global_classfuncs (camel_disco_store_get_type ()));
137         
138         /* virtual method overload */
139         camel_object_class->setv = imap_setv;
140         camel_object_class->getv = imap_getv;
141         
142         camel_service_class->construct = construct;
143         camel_service_class->query_auth_types = query_auth_types;
144         camel_service_class->get_name = imap_get_name;
145         
146         camel_store_class->hash_folder_name = hash_folder_name;
147         camel_store_class->compare_folder_name = compare_folder_name;
148         camel_store_class->create_folder = create_folder;
149         camel_store_class->delete_folder = delete_folder;
150         camel_store_class->rename_folder = rename_folder;
151         camel_store_class->free_folder_info = camel_store_free_folder_info_full;
152         camel_store_class->folder_subscribed = folder_subscribed;
153         camel_store_class->subscribe_folder = subscribe_folder;
154         camel_store_class->unsubscribe_folder = unsubscribe_folder;
155         camel_store_class->noop = imap_noop;
156         
157         camel_disco_store_class->can_work_offline = can_work_offline;
158         camel_disco_store_class->connect_online = imap_connect_online;
159         camel_disco_store_class->connect_offline = imap_connect_offline;
160         camel_disco_store_class->disconnect_online = imap_disconnect_online;
161         camel_disco_store_class->disconnect_offline = imap_disconnect_offline;
162         camel_disco_store_class->get_folder_online = get_folder_online;
163         camel_disco_store_class->get_folder_offline = get_folder_offline;
164         camel_disco_store_class->get_folder_resyncing = get_folder_online;
165         camel_disco_store_class->get_folder_info_online = get_folder_info_online;
166         camel_disco_store_class->get_folder_info_offline = get_folder_info_offline;
167         camel_disco_store_class->get_folder_info_resyncing = get_folder_info_online;
168 }
169
170 static gboolean
171 free_key (gpointer key, gpointer value, gpointer user_data)
172 {
173         g_free (key);
174         return TRUE;
175 }
176
177 static void
178 camel_imap_store_finalize (CamelObject *object)
179 {
180         CamelImapStore *imap_store = CAMEL_IMAP_STORE (object);
181
182         /* This frees current_folder, folders, authtypes, streams, and namespace. */
183         camel_service_disconnect((CamelService *)imap_store, TRUE, NULL);
184
185         if (imap_store->summary) {
186                 camel_store_summary_save((CamelStoreSummary *)imap_store->summary);
187                 camel_object_unref(imap_store->summary);
188         }
189         
190         if (imap_store->base_url)
191                 g_free (imap_store->base_url);
192         if (imap_store->storage_path)
193                 g_free (imap_store->storage_path);
194         
195 #ifdef ENABLE_THREADS
196         e_thread_destroy (imap_store->async_thread);
197 #endif
198 }
199
200 #ifdef ENABLE_THREADS
201 static void async_destroy(EThread *et, EMsg *em, void *data)
202 {
203         CamelImapStore *imap_store = data;
204         CamelImapMsg *msg = (CamelImapMsg *)em;
205         
206         if (msg->free)
207                 msg->free (imap_store, msg);
208         
209         g_free (msg);
210 }
211
212 static void async_received(EThread *et, EMsg *em, void *data)
213 {
214         CamelImapStore *imap_store = data;
215         CamelImapMsg *msg = (CamelImapMsg *)em;
216
217         if (msg->receive)
218                 msg->receive(imap_store, msg);
219 }
220
221 CamelImapMsg *camel_imap_msg_new(void (*receive)(CamelImapStore *store, struct _CamelImapMsg *m),
222                                  void (*free)(CamelImapStore *store, struct _CamelImapMsg *m),
223                                  size_t size)
224 {
225         CamelImapMsg *msg;
226
227         g_assert(size >= sizeof(*msg));
228
229         msg = g_malloc0(size);
230         msg->receive = receive;
231         msg->free = free;
232
233         return msg;
234 }
235
236 void camel_imap_msg_queue(CamelImapStore *store, CamelImapMsg *msg)
237 {
238         e_thread_put(store->async_thread, (EMsg *)msg);
239 }
240
241 #endif
242
243 static void
244 camel_imap_store_init (gpointer object, gpointer klass)
245 {
246         CamelImapStore *imap_store = CAMEL_IMAP_STORE (object);
247         
248         imap_store->istream = NULL;
249         imap_store->ostream = NULL;
250         
251         imap_store->dir_sep = '\0';
252         imap_store->current_folder = NULL;
253         imap_store->connected = FALSE;
254         
255         imap_store->tag_prefix = imap_tag_prefix++;
256         if (imap_tag_prefix > 'Z')
257                 imap_tag_prefix = 'A';
258         
259 #ifdef ENABLE_THREADS
260         imap_store->async_thread = e_thread_new(E_THREAD_QUEUE);
261         e_thread_set_msg_destroy(imap_store->async_thread, async_destroy, imap_store);
262         e_thread_set_msg_received(imap_store->async_thread, async_received, imap_store);
263 #endif /* ENABLE_THREADS */
264 }
265
266 CamelType
267 camel_imap_store_get_type (void)
268 {
269         static CamelType camel_imap_store_type = CAMEL_INVALID_TYPE;
270         
271         if (camel_imap_store_type == CAMEL_INVALID_TYPE)        {
272                 camel_imap_store_type =
273                         camel_type_register (CAMEL_DISCO_STORE_TYPE,
274                                              "CamelImapStore",
275                                              sizeof (CamelImapStore),
276                                              sizeof (CamelImapStoreClass),
277                                              (CamelObjectClassInitFunc) camel_imap_store_class_init,
278                                              NULL,
279                                              (CamelObjectInitFunc) camel_imap_store_init,
280                                              (CamelObjectFinalizeFunc) camel_imap_store_finalize);
281         }
282         
283         return camel_imap_store_type;
284 }
285
286 static void
287 construct (CamelService *service, CamelSession *session,
288            CamelProvider *provider, CamelURL *url,
289            CamelException *ex)
290 {
291         CamelImapStore *imap_store = CAMEL_IMAP_STORE (service);
292         CamelStore *store = CAMEL_STORE (service);
293         char *tmp;
294         CamelURL *summary_url;
295
296         CAMEL_SERVICE_CLASS (parent_class)->construct (service, session, provider, url, ex);
297         if (camel_exception_is_set (ex))
298                 return;
299
300         imap_store->storage_path = camel_session_get_storage_path (session, service, ex);
301         if (!imap_store->storage_path)
302                 return;
303
304         /* FIXME */
305         imap_store->base_url = camel_url_to_string (service->url, (CAMEL_URL_HIDE_PASSWORD |
306                                                                    CAMEL_URL_HIDE_PARAMS |
307                                                                    CAMEL_URL_HIDE_AUTH));
308
309         imap_store->parameters = 0;
310         if (camel_url_get_param (url, "use_lsub"))
311                 store->flags |= CAMEL_STORE_SUBSCRIPTIONS;
312         if (camel_url_get_param (url, "namespace")) {
313                 imap_store->parameters |= IMAP_PARAM_OVERRIDE_NAMESPACE;
314                 g_free(imap_store->namespace);
315                 imap_store->namespace = g_strdup (camel_url_get_param (url, "namespace"));
316         }
317         if (camel_url_get_param (url, "check_all"))
318                 imap_store->parameters |= IMAP_PARAM_CHECK_ALL;
319         if (camel_url_get_param (url, "filter")) {
320                 imap_store->parameters |= IMAP_PARAM_FILTER_INBOX;
321                 store->flags |= CAMEL_STORE_FILTER_INBOX;
322         }
323
324         /* setup/load the store summary */
325         tmp = alloca(strlen(imap_store->storage_path)+32);
326         sprintf(tmp, "%s/.ev-store-summary", imap_store->storage_path);
327         imap_store->summary = camel_imap_store_summary_new();
328         camel_store_summary_set_filename((CamelStoreSummary *)imap_store->summary, tmp);
329         summary_url = camel_url_new(imap_store->base_url, NULL);
330         camel_store_summary_set_uri_base((CamelStoreSummary *)imap_store->summary, summary_url);
331         camel_url_free(summary_url);
332         if (camel_store_summary_load((CamelStoreSummary *)imap_store->summary) == 0) {
333                 CamelImapStoreSummary *is = imap_store->summary;
334
335                 if (is->namespace) {
336                         /* if namespace has changed, clear folder list */
337                         if (imap_store->namespace && strcmp(imap_store->namespace, is->namespace->full_name) != 0) {
338                                 camel_store_summary_clear((CamelStoreSummary *)is);
339                         } else {
340                                 imap_store->namespace = g_strdup(is->namespace->full_name);
341                                 imap_store->dir_sep = is->namespace->sep;
342                                 store->dir_sep = is->namespace->sep;
343                         }
344                 }
345  
346                 imap_store->capabilities = is->capabilities;
347                 imap_set_server_level(imap_store);
348         }
349 }
350
351 static int
352 imap_setv (CamelObject *object, CamelException *ex, CamelArgV *args)
353 {
354         CamelImapStore *store = (CamelImapStore *) object;
355         guint32 tag, flags;
356         int i;
357         
358         for (i = 0; i < args->argc; i++) {
359                 tag = args->argv[i].tag;
360                 
361                 /* make sure this arg wasn't already handled */
362                 if (tag & CAMEL_ARG_IGNORE)
363                         continue;
364                 
365                 /* make sure this is an arg we're supposed to handle */
366                 if ((tag & CAMEL_ARG_TAG) <= CAMEL_IMAP_STORE_ARG_FIRST ||
367                     (tag & CAMEL_ARG_TAG) >= CAMEL_IMAP_STORE_ARG_FIRST + 100)
368                         continue;
369                 
370                 if (tag == CAMEL_IMAP_STORE_NAMESPACE) {
371                         if (strcmp (store->namespace, args->argv[i].ca_str) != 0) {
372                                 g_free (store->namespace);
373                                 store->namespace = g_strdup (args->argv[i].ca_str);
374                                 /* the current imap code will need to do a reconnect for this to take effect */
375                                 /*reconnect = TRUE;*/
376                         }
377                 } else if (tag == CAMEL_IMAP_STORE_OVERRIDE_NAMESPACE) {
378                         flags = args->argv[i].ca_int ? IMAP_PARAM_OVERRIDE_NAMESPACE : 0;
379                         flags |= (store->parameters & ~IMAP_PARAM_OVERRIDE_NAMESPACE);
380                         
381                         if (store->parameters != flags) {
382                                 store->parameters = flags;
383                                 /* the current imap code will need to do a reconnect for this to take effect */
384                                 /*reconnect = TRUE;*/
385                         }
386                 } else if (tag == CAMEL_IMAP_STORE_CHECK_ALL) {
387                         flags = args->argv[i].ca_int ? IMAP_PARAM_CHECK_ALL : 0;
388                         flags |= (store->parameters & ~IMAP_PARAM_CHECK_ALL);
389                         store->parameters = flags;
390                         /* no need to reconnect for this option to take effect... */
391                 } else if (tag == CAMEL_IMAP_STORE_FILTER_INBOX) {
392                         flags = args->argv[i].ca_int ? IMAP_PARAM_FILTER_INBOX : 0;
393                         flags |= (store->parameters & ~IMAP_PARAM_FILTER_INBOX);
394                         store->parameters = flags;
395                         /* no need to reconnect for this option to take effect... */
396                 } else {
397                         /* error?? */
398                         continue;
399                 }
400                 
401                 /* let our parent know that we've handled this arg */
402                 camel_argv_ignore (args, i);
403         }
404         
405         /* FIXME: if we need to reconnect for a change to take affect,
406            we need to do it here... or, better yet, somehow chain it
407            up to CamelService's setv implementation. */
408         
409         return CAMEL_OBJECT_CLASS (parent_class)->setv (object, ex, args);
410 }
411
412 static int
413 imap_getv (CamelObject *object, CamelException *ex, CamelArgGetV *args)
414 {
415         CamelImapStore *store = (CamelImapStore *) object;
416         guint32 tag;
417         int i;
418         
419         for (i = 0; i < args->argc; i++) {
420                 tag = args->argv[i].tag;
421                 
422                 /* make sure this is an arg we're supposed to handle */
423                 if ((tag & CAMEL_ARG_TAG) <= CAMEL_IMAP_STORE_ARG_FIRST ||
424                     (tag & CAMEL_ARG_TAG) >= CAMEL_IMAP_STORE_ARG_FIRST + 100)
425                         continue;
426                 
427                 switch (tag) {
428                 case CAMEL_IMAP_STORE_NAMESPACE:
429                         /* get the username */
430                         *args->argv[i].ca_str = store->namespace;
431                         break;
432                 case CAMEL_IMAP_STORE_OVERRIDE_NAMESPACE:
433                         /* get the auth mechanism */
434                         *args->argv[i].ca_int = store->parameters & IMAP_PARAM_OVERRIDE_NAMESPACE ? TRUE : FALSE;
435                         break;
436                 case CAMEL_IMAP_STORE_CHECK_ALL:
437                         /* get the hostname */
438                         *args->argv[i].ca_int = store->parameters & IMAP_PARAM_CHECK_ALL ? TRUE : FALSE;
439                         break;
440                 case CAMEL_IMAP_STORE_FILTER_INBOX:
441                         /* get the port */
442                         *args->argv[i].ca_int = store->parameters & IMAP_PARAM_FILTER_INBOX ? TRUE : FALSE;
443                         break;
444                 default:
445                         /* error? */
446                         break;
447                 }
448         }
449         
450         return CAMEL_OBJECT_CLASS (parent_class)->getv (object, ex, args);
451 }
452
453 static char *
454 imap_get_name (CamelService *service, gboolean brief)
455 {
456         if (brief)
457                 return g_strdup_printf (_("IMAP server %s"), service->url->host);
458         else
459                 return g_strdup_printf (_("IMAP service for %s on %s"),
460                                         service->url->user, service->url->host);
461 }
462
463 static void
464 imap_set_server_level (CamelImapStore *store)
465 {
466         if (store->capabilities & IMAP_CAPABILITY_IMAP4REV1) {
467                 store->server_level = IMAP_LEVEL_IMAP4REV1;
468                 store->capabilities |= IMAP_CAPABILITY_STATUS;
469         } else if (store->capabilities & IMAP_CAPABILITY_IMAP4)
470                 store->server_level = IMAP_LEVEL_IMAP4;
471         else
472                 store->server_level = IMAP_LEVEL_UNKNOWN;
473 }
474
475 static struct {
476         const char *name;
477         guint32 flag;
478 } capabilities[] = {
479         { "IMAP4",              IMAP_CAPABILITY_IMAP4 },
480         { "IMAP4REV1",          IMAP_CAPABILITY_IMAP4REV1 },
481         { "STATUS",             IMAP_CAPABILITY_STATUS },
482         { "NAMESPACE",          IMAP_CAPABILITY_NAMESPACE },
483         { "UIDPLUS",            IMAP_CAPABILITY_UIDPLUS },
484         { "LITERAL+",           IMAP_CAPABILITY_LITERALPLUS },
485         { "STARTTLS",           IMAP_CAPABILITY_STARTTLS },
486         { NULL, 0 }
487 };
488
489
490 static gboolean
491 imap_get_capability (CamelService *service, CamelException *ex)
492 {
493         CamelImapStore *store = CAMEL_IMAP_STORE (service);
494         CamelImapResponse *response;
495         char *result, *capa, *lasts;
496         int i;
497         
498         CAMEL_SERVICE_ASSERT_LOCKED (store, connect_lock);
499         
500         /* Find out the IMAP capabilities */
501         /* We assume we have utf8 capable search until a failed search tells us otherwise */
502         store->capabilities = IMAP_CAPABILITY_utf8_search;
503         store->authtypes = g_hash_table_new (g_str_hash, g_str_equal);
504         response = camel_imap_command (store, NULL, ex, "CAPABILITY");
505         if (!response)
506                 return FALSE;
507         result = camel_imap_response_extract (store, response, "CAPABILITY ", ex);
508         if (!result)
509                 return FALSE;
510         
511         /* Skip over "* CAPABILITY ". */
512         capa = result + 13;
513         for (capa = strtok_r (capa, " ", &lasts); capa;
514              capa = strtok_r (NULL, " ", &lasts)) {
515                 if (!strncmp (capa, "AUTH=", 5)) {
516                         g_hash_table_insert (store->authtypes,
517                                              g_strdup (capa + 5),
518                                              GINT_TO_POINTER (1));
519                         continue;
520                 }
521                 for (i = 0; capabilities[i].name; i++) {
522                         if (strcasecmp (capa, capabilities[i].name) == 0) {
523                                 store->capabilities |= capabilities[i].flag;
524                                 break;
525                         }
526                 }
527         }
528         g_free (result);
529         
530         imap_set_server_level (store);
531         
532         if (store->summary->capabilities != store->capabilities) {
533                 store->summary->capabilities = store->capabilities;
534                 camel_store_summary_touch((CamelStoreSummary *)store->summary);
535                 camel_store_summary_save((CamelStoreSummary *)store->summary);
536         }
537         
538         return TRUE;
539 }
540
541 enum {
542         USE_SSL_NEVER,
543         USE_SSL_ALWAYS,
544         USE_SSL_WHEN_POSSIBLE
545 };
546
547 #define SSL_PORT_FLAGS (CAMEL_TCP_STREAM_SSL_ENABLE_SSL2 | CAMEL_TCP_STREAM_SSL_ENABLE_SSL3)
548 #define STARTTLS_FLAGS (CAMEL_TCP_STREAM_SSL_ENABLE_TLS)
549
550 static gboolean
551 connect_to_server (CamelService *service, int ssl_mode, int try_starttls, CamelException *ex)
552 {
553         CamelImapStore *store = (CamelImapStore *) service;
554         CamelImapResponse *response;
555         CamelStream *tcp_stream;
556         struct hostent *h;
557         int clean_quit;
558         int port, ret;
559         char *buf;
560         
561         h = camel_service_gethost (service, ex);
562         if (!h)
563                 return FALSE;
564         
565         port = service->url->port ? service->url->port : 143;
566         
567 #ifdef HAVE_SSL
568         if (ssl_mode != USE_SSL_NEVER) {
569                 if (try_starttls) {
570                         tcp_stream = camel_tcp_stream_ssl_new_raw (service, service->url->host, STARTTLS_FLAGS);
571                 } else {
572                         port = service->url->port ? service->url->port : 993;
573                         tcp_stream = camel_tcp_stream_ssl_new (service, service->url->host, SSL_PORT_FLAGS);
574                 }
575         } else {
576                 tcp_stream = camel_tcp_stream_raw_new ();
577         }
578 #else
579         tcp_stream = camel_tcp_stream_raw_new ();
580 #endif /* HAVE_SSL */
581         
582         printf ("trying to connect to %s:%d...", service->url->host, port);
583         
584         ret = camel_tcp_stream_connect (CAMEL_TCP_STREAM (tcp_stream), h, port);
585         camel_free_host (h);
586         if (ret == -1) {
587                 if (errno == EINTR)
588                         camel_exception_set (ex, CAMEL_EXCEPTION_USER_CANCEL,
589                                              _("Connection cancelled"));
590                 else
591                         camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
592                                               _("Could not connect to %s (port %d): %s"),
593                                               service->url->host, port, g_strerror (errno));
594                 
595                 printf ("failed: %s\n", g_strerror (errno));
596                 
597                 camel_object_unref (CAMEL_OBJECT (tcp_stream));
598                 
599                 return FALSE;
600         }
601         
602         printf ("connected...");
603         
604         store->ostream = tcp_stream;
605         store->istream = camel_stream_buffer_new (tcp_stream, CAMEL_STREAM_BUFFER_READ);
606         
607         store->connected = TRUE;
608         store->command = 0;
609         
610         /* Read the greeting, if any. FIXME: deal with PREAUTH */
611         if (camel_imap_store_readline (store, &buf, ex) < 0) {
612                 printf ("failed when trying to read greeting: %s\n", g_strerror (errno));
613                 
614                 if (store->istream) {
615                         camel_object_unref (CAMEL_OBJECT (store->istream));
616                         store->istream = NULL;
617                 }
618                 
619                 if (store->ostream) {
620                         camel_object_unref (CAMEL_OBJECT (store->ostream));
621                         store->ostream = NULL;
622                 }
623                 
624                 store->connected = FALSE;
625                 
626                 return FALSE;
627         }
628         g_free (buf);
629         
630         /* get the imap server capabilities */
631         if (!imap_get_capability (service, ex)) {
632                 printf ("failed to get capabilities: %s\n", g_strerror (errno));
633                 
634                 if (store->istream) {
635                         camel_object_unref (CAMEL_OBJECT (store->istream));
636                         store->istream = NULL;
637                 }
638                 
639                 if (store->ostream) {
640                         camel_object_unref (CAMEL_OBJECT (store->ostream));
641                         store->ostream = NULL;
642                 }
643                 
644                 store->connected = FALSE;
645                 return FALSE;
646         }
647         
648 #ifdef HAVE_SSL
649         if (ssl_mode == USE_SSL_WHEN_POSSIBLE) {
650                 if (store->capabilities & IMAP_CAPABILITY_STARTTLS)
651                         goto starttls;
652         } else if (ssl_mode == USE_SSL_ALWAYS) {
653                 if (try_starttls) {
654                         if (store->capabilities & IMAP_CAPABILITY_STARTTLS) {
655                                 /* attempt to toggle STARTTLS mode */
656                                 goto starttls;
657                         } else {
658                                 /* server doesn't support STARTTLS, abort */
659                                 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
660                                                       _("Failed to connect to IMAP server %s in secure mode: %s"),
661                                                       service->url->host, _("SSL/TLS extension not supported."));
662                                 /* we have the possibility of quitting cleanly here */
663                                 clean_quit = TRUE;
664                                 goto exception;
665                         }
666                 }
667         }
668 #endif /* HAVE_SSL */
669         
670         printf ("success\n");
671         
672         return TRUE;
673         
674 #ifdef HAVE_SSL
675  starttls:
676         
677         /* as soon as we send a STARTTLS command, all hope is lost of a clean QUIT if problems arise */
678         clean_quit = FALSE;
679         
680         response = camel_imap_command (store, NULL, ex, "STARTTLS");
681         if (!response) {
682                 printf ("STARTTLS command failed: %s\n", g_strerror (errno));
683                 camel_object_unref (CAMEL_OBJECT (store->istream));
684                 camel_object_unref (CAMEL_OBJECT (store->ostream));
685                 store->istream = store->ostream = NULL;
686                 return FALSE;
687         }
688         
689         camel_imap_response_free_without_processing (store, response);
690         
691         /* Okay, now toggle SSL/TLS mode */
692         if (camel_tcp_stream_ssl_enable_ssl (CAMEL_TCP_STREAM_SSL (tcp_stream)) == -1) {
693                 printf ("failed toggling into STARTTLS mode: %s\n", g_strerror (errno));
694                 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
695                                       _("Failed to connect to IMAP server %s in secure mode: %s"),
696                                       service->url->host, _("SSL negotiations failed"));
697                 goto exception;
698         }
699         
700         /* rfc2595, section 4 states that after a successful STLS
701            command, the client MUST discard prior CAPA responses */
702         if (!imap_get_capability (service, ex)) {
703                 printf ("failed getting capabilities after STARTLS: %s\n", g_strerror (errno));
704                 
705                 if (store->istream) {
706                         camel_object_unref (CAMEL_OBJECT (store->istream));
707                         store->istream = NULL;
708                 }
709                 
710                 if (store->ostream) {
711                         camel_object_unref (CAMEL_OBJECT (store->ostream));
712                         store->ostream = NULL;
713                 }
714                 
715                 store->connected = FALSE;
716                 
717                 return FALSE;
718         }
719         
720         printf ("success\n");
721         
722         return TRUE;
723         
724  exception:
725         
726         if (clean_quit && store->connected) {
727                 /* try to disconnect cleanly */
728                 response = camel_imap_command (store, NULL, ex, "LOGOUT");
729                 if (response)
730                         camel_imap_response_free_without_processing (store, response);
731         }
732         
733         if (store->istream) {
734                 camel_object_unref (CAMEL_OBJECT (store->istream));
735                 store->istream = NULL;
736         }
737         
738         if (store->ostream) {
739                 camel_object_unref (CAMEL_OBJECT (store->ostream));
740                 store->ostream = NULL;
741         }
742         
743         store->connected = FALSE;
744         
745         return FALSE;
746 #endif /* HAVE_SSL */
747 }
748
749 static struct {
750         char *value;
751         int mode;
752 } ssl_options[] = {
753         { "",              USE_SSL_ALWAYS        },
754         { "always",        USE_SSL_ALWAYS        },
755         { "when-possible", USE_SSL_WHEN_POSSIBLE },
756         { "never",         USE_SSL_NEVER         },
757         { NULL,            USE_SSL_NEVER         },
758 };
759
760 static gboolean
761 connect_to_server_wrapper (CamelService *service, CamelException *ex)
762 {
763 #ifdef HAVE_SSL
764         const char *use_ssl;
765         int i, ssl_mode;
766         
767         use_ssl = camel_url_get_param (service->url, "use_ssl");
768         if (use_ssl) {
769                 for (i = 0; ssl_options[i].value; i++)
770                         if (!strcmp (ssl_options[i].value, use_ssl))
771                                 break;
772                 ssl_mode = ssl_options[i].mode;
773         } else
774                 ssl_mode = USE_SSL_NEVER;
775         
776         if (ssl_mode == USE_SSL_ALWAYS) {
777                 /* First try the ssl port */
778                 if (!connect_to_server (service, ssl_mode, FALSE, ex)) {
779                         if (camel_exception_get_id (ex) == CAMEL_EXCEPTION_SERVICE_UNAVAILABLE) {
780                                 /* The ssl port seems to be unavailable, lets try STARTTLS */
781                                 camel_exception_clear (ex);
782                                 return connect_to_server (service, ssl_mode, TRUE, ex);
783                         } else {
784                                 return FALSE;
785                         }
786                 }
787                 
788                 return TRUE;
789         } else if (ssl_mode == USE_SSL_WHEN_POSSIBLE) {
790                 /* If the server supports STARTTLS, use it */
791                 return connect_to_server (service, ssl_mode, TRUE, ex);
792         } else {
793                 /* User doesn't care about SSL */
794                 return connect_to_server (service, ssl_mode, FALSE, ex);
795         }
796 #else
797         return connect_to_server (service, USE_SSL_NEVER, FALSE, ex);
798 #endif
799 }
800
801 extern CamelServiceAuthType camel_imap_password_authtype;
802
803 static GList *
804 query_auth_types (CamelService *service, CamelException *ex)
805 {
806         CamelImapStore *store = CAMEL_IMAP_STORE (service);
807         CamelServiceAuthType *authtype;
808         GList *sasl_types, *t, *next;
809         gboolean connected;
810         
811         if (!camel_disco_store_check_online (CAMEL_DISCO_STORE (store), ex))
812                 return NULL;
813         
814         CAMEL_SERVICE_LOCK (store, connect_lock);
815         connected = connect_to_server_wrapper (service, ex);
816         CAMEL_SERVICE_UNLOCK (store, connect_lock);
817         if (!connected)
818                 return NULL;
819         
820         sasl_types = camel_sasl_authtype_list (FALSE);
821         for (t = sasl_types; t; t = next) {
822                 authtype = t->data;
823                 next = t->next;
824                 
825                 if (!g_hash_table_lookup (store->authtypes, authtype->authproto)) {
826                         sasl_types = g_list_remove_link (sasl_types, t);
827                         g_list_free_1 (t);
828                 }
829         }
830         
831         return g_list_prepend (sasl_types, &camel_imap_password_authtype);
832 }
833
834 /* folder_name is path name */
835 static CamelFolderInfo *
836 imap_build_folder_info(CamelImapStore *imap_store, const char *folder_name)
837 {
838         CamelURL *url;
839         const char *name;
840         CamelFolderInfo *fi;
841
842         fi = g_malloc0(sizeof(*fi));
843
844         fi->full_name = g_strdup(folder_name);
845         fi->unread_message_count = 0;
846
847         url = camel_url_new (imap_store->base_url, NULL);
848         g_free (url->path);
849         url->path = g_strdup_printf ("/%s", folder_name);
850         fi->url = camel_url_to_string (url, CAMEL_URL_HIDE_ALL);
851         camel_url_free(url);
852         fi->path = g_strdup_printf("/%s", folder_name);
853         name = strrchr (fi->path, '/');
854         if (name)
855                 name++;
856         else
857                 name = fi->path;
858
859         fi->name = g_strdup (name);
860
861         return fi;
862 }
863
864 static void
865 imap_folder_effectively_unsubscribed(CamelImapStore *imap_store, 
866                                      const char *folder_name, CamelException *ex)
867 {
868         CamelFolderInfo *fi;
869         CamelStoreInfo *si;
870
871         si = camel_store_summary_path((CamelStoreSummary *)imap_store->summary, folder_name);
872         if (si) {
873                 if (si->flags & CAMEL_STORE_INFO_FOLDER_SUBSCRIBED) {
874                         si->flags &= ~CAMEL_STORE_INFO_FOLDER_SUBSCRIBED;
875                         camel_store_summary_touch((CamelStoreSummary *)imap_store->summary);
876                         camel_store_summary_save((CamelStoreSummary *)imap_store->summary);
877                 }
878                 camel_store_summary_info_free((CamelStoreSummary *)imap_store->summary, si);
879         }
880
881         if (imap_store->renaming) {
882                 /* we don't need to emit a "folder_unsubscribed" signal
883                    if we are in the process of renaming folders, so we
884                    are done here... */
885                 return;
886
887         }
888
889         fi = imap_build_folder_info(imap_store, folder_name);
890         camel_object_trigger_event (CAMEL_OBJECT (imap_store), "folder_unsubscribed", fi);
891         camel_folder_info_free (fi);
892 }
893
894 static void
895 imap_forget_folder (CamelImapStore *imap_store, const char *folder_name, CamelException *ex)
896 {
897         CamelFolderSummary *summary;
898         CamelImapMessageCache *cache;
899         char *summary_file;
900         char *journal_file;
901         char *folder_dir, *storage_path;
902         CamelFolderInfo *fi;
903         const char *name;
904
905         name = strrchr (folder_name, imap_store->dir_sep);
906         if (name)
907                 name++;
908         else
909                 name = folder_name;
910         
911         storage_path = g_strdup_printf ("%s/folders", imap_store->storage_path);
912         folder_dir = e_path_to_physical (storage_path, folder_name);
913         g_free (storage_path);
914         if (access (folder_dir, F_OK) != 0) {
915                 g_free (folder_dir);
916                 goto event;
917         }
918         
919         summary_file = g_strdup_printf ("%s/summary", folder_dir);
920         summary = camel_imap_summary_new (summary_file);
921         if (!summary) {
922                 g_free (summary_file);
923                 g_free (folder_dir);
924                 goto event;
925         }
926         
927         cache = camel_imap_message_cache_new (folder_dir, summary, ex);
928         if (cache)
929                 camel_imap_message_cache_clear (cache);
930         
931         camel_object_unref (cache);
932         camel_object_unref (summary);
933         
934         unlink (summary_file);
935         g_free (summary_file);
936         
937         journal_file = g_strdup_printf ("%s/summary", folder_dir);
938         unlink (journal_file);
939         g_free (journal_file);
940         
941         rmdir (folder_dir);
942         g_free (folder_dir);
943         
944  event:
945
946         camel_store_summary_remove_path((CamelStoreSummary *)imap_store->summary, folder_name);
947         camel_store_summary_save((CamelStoreSummary *)imap_store->summary);
948
949         fi = imap_build_folder_info(imap_store, folder_name);
950         camel_object_trigger_event (CAMEL_OBJECT (imap_store), "folder_deleted", fi);
951         camel_folder_info_free (fi);
952 }
953
954 static gboolean
955 imap_check_folder_still_extant (CamelImapStore *imap_store, const char *full_name, 
956                                 CamelException *ex)
957 {
958         CamelImapResponse *response;
959
960         response = camel_imap_command (imap_store, NULL, ex, "LIST \"\" %S",
961                                        full_name);
962
963         if (response) {
964                 gboolean stillthere = response->untagged->len != 0;
965
966                 camel_imap_response_free_without_processing (imap_store, response);
967
968                 return stillthere;
969         }
970
971         /* if the command was rejected, there must be some other error,
972            assume it worked so we dont blow away the folder unecessarily */
973         return TRUE;
974 }
975
976 /* This is a little 'hack' to avoid the deadlock conditions that would otherwise
977    ensue when calling camel_folder_refresh_info from inside a lock */
978 /* NB: on second thougts this is probably not entirely safe, but it'll do for now */
979 /* No, its definetly not safe.  So its been changed to copy the folders first */
980 /* the alternative is to:
981    make the camel folder->lock recursive (which should probably be done)
982    or remove it from camel_folder_refresh_info, and use another locking mechanism */
983 /* also see get_folder_info_online() for the same hack repeated */
984 static void
985 imap_store_refresh_folders (CamelImapStore *store, CamelException *ex)
986 {
987         GPtrArray *folders;
988         int i;
989         
990         folders = camel_object_bag_list(CAMEL_STORE (store)->folders);
991         
992         for (i = 0; i <folders->len; i++) {
993                 CamelFolder *folder = folders->pdata[i];
994
995                 CAMEL_IMAP_FOLDER (folder)->need_rescan = TRUE;
996                 if (!camel_exception_is_set(ex))
997                         CAMEL_FOLDER_CLASS (CAMEL_OBJECT_GET_CLASS(folder))->refresh_info(folder, ex);
998
999                 if (camel_exception_is_set (ex) &&
1000                     imap_check_folder_still_extant (store, folder->full_name, ex) == FALSE) {
1001                         gchar *namedup;
1002                         
1003                         /* the folder was deleted (may happen when we come back online
1004                          * after being offline */
1005                         
1006                         namedup = g_strdup (folder->full_name);
1007                         camel_object_unref(folder);
1008                         imap_folder_effectively_unsubscribed (store, namedup, ex);
1009                         imap_forget_folder (store, namedup, ex);
1010                         g_free (namedup);
1011                 } else
1012                         camel_object_unref(folder);
1013         }
1014         
1015         g_ptr_array_free (folders, TRUE);
1016 }       
1017
1018 static gboolean
1019 try_auth (CamelImapStore *store, const char *mech, CamelException *ex)
1020 {
1021         CamelSasl *sasl;
1022         CamelImapResponse *response;
1023         char *resp;
1024         char *sasl_resp;
1025         
1026         CAMEL_SERVICE_ASSERT_LOCKED (store, connect_lock);
1027         
1028         response = camel_imap_command (store, NULL, ex, "AUTHENTICATE %s", mech);
1029         if (!response)
1030                 return FALSE;
1031         
1032         sasl = camel_sasl_new ("imap", mech, CAMEL_SERVICE (store));
1033         while (!camel_sasl_authenticated (sasl)) {
1034                 resp = camel_imap_response_extract_continuation (store, response, ex);
1035                 if (!resp)
1036                         goto lose;
1037                 
1038                 sasl_resp = camel_sasl_challenge_base64 (sasl, imap_next_word (resp), ex);
1039                 g_free (resp);
1040                 if (camel_exception_is_set (ex))
1041                         goto break_and_lose;
1042                 
1043                 response = camel_imap_command_continuation (store, sasl_resp, strlen (sasl_resp), ex);
1044                 g_free (sasl_resp);
1045                 if (!response)
1046                         goto lose;
1047         }
1048         
1049         resp = camel_imap_response_extract_continuation (store, response, NULL);
1050         if (resp) {
1051                 /* Oops. SASL claims we're done, but the IMAP server
1052                  * doesn't think so...
1053                  */
1054                 g_free (resp);
1055                 goto lose;
1056         }
1057         
1058         camel_object_unref (CAMEL_OBJECT (sasl));
1059         
1060         return TRUE;
1061         
1062  break_and_lose:
1063         /* Get the server out of "waiting for continuation data" mode. */
1064         response = camel_imap_command_continuation (store, "*", 1, NULL);
1065         if (response)
1066                 camel_imap_response_free (store, response);
1067         
1068  lose:
1069         if (!camel_exception_is_set (ex)) {
1070                 camel_exception_set (ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE,
1071                                      _("Bad authentication response from server."));
1072         }
1073         
1074         camel_object_unref (CAMEL_OBJECT (sasl));
1075         
1076         return FALSE;
1077 }
1078
1079 static gboolean
1080 imap_auth_loop (CamelService *service, CamelException *ex)
1081 {
1082         CamelImapStore *store = CAMEL_IMAP_STORE (service);
1083         CamelSession *session = camel_service_get_session (service);
1084         CamelServiceAuthType *authtype = NULL;
1085         CamelImapResponse *response;
1086         char *errbuf = NULL;
1087         gboolean authenticated = FALSE;
1088         
1089         CAMEL_SERVICE_ASSERT_LOCKED (store, connect_lock);
1090         
1091         if (service->url->authmech) {
1092                 if (!g_hash_table_lookup (store->authtypes, service->url->authmech)) {
1093                         camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE,
1094                                               _("IMAP server %s does not support requested "
1095                                                 "authentication type %s"),
1096                                               service->url->host,
1097                                               service->url->authmech);
1098                         return FALSE;
1099                 }
1100                 
1101                 authtype = camel_sasl_authtype (service->url->authmech);
1102                 if (!authtype) {
1103                         camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE,
1104                                               _("No support for authentication type %s"),
1105                                               service->url->authmech);
1106                         return FALSE;
1107                 }
1108                 
1109                 if (!authtype->need_password) {
1110                         authenticated = try_auth (store, authtype->authproto, ex);
1111                         if (!authenticated)
1112                                 return FALSE;
1113                 }
1114         }
1115         
1116         while (!authenticated) {
1117                 if (errbuf) {
1118                         /* We need to un-cache the password before prompting again */
1119                         camel_session_forget_password (session, service, "password", ex);
1120                         g_free (service->url->passwd);
1121                         service->url->passwd = NULL;
1122                 }
1123                 
1124                 if (!service->url->passwd) {
1125                         char *prompt;
1126                         
1127                         prompt = g_strdup_printf (_("%sPlease enter the IMAP "
1128                                                     "password for %s@%s"),
1129                                                   errbuf ? errbuf : "",
1130                                                   service->url->user,
1131                                                   service->url->host);
1132                         service->url->passwd =
1133                                 camel_session_get_password (session, prompt, FALSE, TRUE,
1134                                                             service, "password", ex);
1135                         g_free (prompt);
1136                         g_free (errbuf);
1137                         errbuf = NULL;
1138                         
1139                         if (!service->url->passwd) {
1140                                 camel_exception_set (ex, CAMEL_EXCEPTION_USER_CANCEL,
1141                                                      _("You didn't enter a password."));
1142                                 return FALSE;
1143                         }
1144                 }
1145                 
1146                 if (!store->connected) {
1147                         /* Some servers (eg, courier) will disconnect on
1148                          * a bad password. So reconnect here.
1149                          */
1150                         if (!connect_to_server_wrapper (service, ex))
1151                                 return FALSE;
1152                 }
1153                 
1154                 if (authtype)
1155                         authenticated = try_auth (store, authtype->authproto, ex);
1156                 else {
1157                         response = camel_imap_command (store, NULL, ex,
1158                                                        "LOGIN %S %S",
1159                                                        service->url->user,
1160                                                        service->url->passwd);
1161                         if (response) {
1162                                 camel_imap_response_free (store, response);
1163                                 authenticated = TRUE;
1164                         }
1165                 }
1166                 if (!authenticated) {
1167                         if (camel_exception_get_id(ex) == CAMEL_EXCEPTION_USER_CANCEL)
1168                                 return FALSE;
1169                         
1170                         errbuf = g_strdup_printf (_("Unable to authenticate "
1171                                                     "to IMAP server.\n%s\n\n"),
1172                                                   camel_exception_get_description (ex));
1173                         camel_exception_clear (ex);
1174                 }
1175         }
1176         
1177         return TRUE;
1178 }
1179
1180 static gboolean
1181 can_work_offline (CamelDiscoStore *disco_store)
1182 {
1183         CamelImapStore *store = CAMEL_IMAP_STORE (disco_store);
1184         
1185         return camel_store_summary_count((CamelStoreSummary *)store->summary) != 0;
1186 }
1187
1188 static gboolean
1189 imap_connect_online (CamelService *service, CamelException *ex)
1190 {
1191         CamelImapStore *store = CAMEL_IMAP_STORE (service);
1192         CamelDiscoStore *disco_store = CAMEL_DISCO_STORE (service);
1193         CamelImapResponse *response;
1194         /*struct _namespaces *namespaces;*/
1195         char *result, *name, *path;
1196         int i;
1197         size_t len;
1198         CamelImapStoreNamespace *ns;
1199
1200         CAMEL_SERVICE_LOCK (store, connect_lock);
1201         if (!connect_to_server_wrapper (service, ex) ||
1202             !imap_auth_loop (service, ex)) {
1203                 CAMEL_SERVICE_UNLOCK (store, connect_lock);
1204                 camel_service_disconnect (service, TRUE, NULL);
1205                 return FALSE;
1206         }
1207         
1208         /* Get namespace and hierarchy separator */
1209         if ((store->capabilities & IMAP_CAPABILITY_NAMESPACE) &&
1210             !(store->parameters & IMAP_PARAM_OVERRIDE_NAMESPACE)) {
1211                 response = camel_imap_command (store, NULL, ex, "NAMESPACE");
1212                 if (!response)
1213                         goto done;
1214                 
1215                 result = camel_imap_response_extract (store, response, "NAMESPACE", ex);
1216                 if (!result)
1217                         goto done;
1218                 
1219 #if 0
1220                 /* new code... */
1221                 namespaces = imap_parse_namespace_response (result);
1222                 imap_namespaces_destroy (namespaces);
1223                 /* end new code */
1224 #endif
1225                 
1226                 name = camel_strstrcase (result, "NAMESPACE ((");
1227                 if (name) {
1228                         char *sep;
1229                         
1230                         name += 12;
1231                         store->namespace = imap_parse_string ((const char **) &name, &len);
1232                         if (name && *name++ == ' ') {
1233                                 sep = imap_parse_string ((const char **) &name, &len);
1234                                 if (sep) {
1235                                         store->dir_sep = *sep;
1236                                         ((CamelStore *)store)->dir_sep = store->dir_sep;
1237                                         g_free (sep);
1238                                 }
1239                         }
1240                 }
1241                 g_free (result);
1242         }
1243         
1244         if (!store->namespace)
1245                 store->namespace = g_strdup ("");
1246         
1247         if (!store->dir_sep) {
1248                 if (store->server_level >= IMAP_LEVEL_IMAP4REV1) {
1249                         /* This idiom means "tell me the hierarchy separator
1250                          * for the given path, even if that path doesn't exist.
1251                          */
1252                         response = camel_imap_command (store, NULL, ex,
1253                                                        "LIST %S \"\"",
1254                                                        store->namespace);
1255                 } else {
1256                         /* Plain IMAP4 doesn't have that idiom, so we fall back
1257                          * to "tell me about this folder", which will fail if
1258                          * the folder doesn't exist (eg, if namespace is "").
1259                          */
1260                         response = camel_imap_command (store, NULL, ex,
1261                                                        "LIST \"\" %S",
1262                                                        store->namespace);
1263                 }
1264                 if (!response)
1265                         goto done;
1266                 
1267                 result = camel_imap_response_extract (store, response, "LIST", NULL);
1268                 if (result) {
1269                         imap_parse_list_response (store, result, NULL, &store->dir_sep, NULL);
1270                         g_free (result);
1271                 }
1272                 if (!store->dir_sep) {
1273                         store->dir_sep = '/';   /* Guess */
1274                         ((CamelStore *)store)->dir_sep = store->dir_sep;
1275                 }
1276         }
1277         
1278         /* canonicalize the namespace to end with dir_sep */
1279         len = strlen (store->namespace);
1280         if (len && store->namespace[len - 1] != store->dir_sep) {
1281                 gchar *tmp;
1282                 
1283                 tmp = g_strdup_printf ("%s%c", store->namespace, store->dir_sep);
1284                 g_free (store->namespace);
1285                 store->namespace = tmp;
1286         }
1287         
1288         ns = camel_imap_store_summary_namespace_new(store->summary, store->namespace, store->dir_sep);
1289         camel_imap_store_summary_namespace_set(store->summary, ns);
1290         
1291         if (CAMEL_STORE (store)->flags & CAMEL_STORE_SUBSCRIPTIONS) {
1292                 gboolean haveinbox = FALSE;
1293                 GPtrArray *folders;
1294                 char *pattern;
1295                 
1296                 /* this pre-fills the summary, and checks that lsub is useful */
1297                 folders = g_ptr_array_new ();
1298                 pattern = g_strdup_printf ("%s*", store->namespace);
1299                 get_folders_online (store, pattern, folders, TRUE, ex);
1300                 g_free (pattern);
1301                 
1302                 for (i = 0; i < folders->len; i++) {
1303                         CamelFolderInfo *fi = folders->pdata[i];
1304                         
1305                         haveinbox = haveinbox || !strcasecmp (fi->full_name, "INBOX");
1306                         
1307                         if (fi->flags & (CAMEL_IMAP_FOLDER_MARKED | CAMEL_IMAP_FOLDER_UNMARKED))
1308                                 store->capabilities |= IMAP_CAPABILITY_useful_lsub;
1309                         camel_folder_info_free (fi);
1310                 }
1311                 
1312                 /* if the namespace is under INBOX, check INBOX explicitly */
1313                 if (!strncasecmp (store->namespace, "INBOX", 5) && !camel_exception_is_set (ex)) {
1314                         gboolean just_subscribed = FALSE;
1315                         gboolean need_subscribe = FALSE;
1316                         
1317                 recheck:
1318                         g_ptr_array_set_size (folders, 0);
1319                         get_folders_online (store, "INBOX", folders, TRUE, ex);
1320                         
1321                         for (i = 0; i < folders->len; i++) {
1322                                 CamelFolderInfo *fi = folders->pdata[i];
1323                                 
1324                                 /* this should always be TRUE if folders->len > 0 */
1325                                 if (!strcasecmp (fi->full_name, "INBOX")) {
1326                                         haveinbox = TRUE;
1327                                         
1328                                         /* if INBOX is marked as \NoSelect then it is probably
1329                                            because it has not been subscribed to */
1330                                         if (!need_subscribe)
1331                                                 need_subscribe = fi->flags & CAMEL_FOLDER_NOSELECT;
1332                                 }
1333                                 
1334                                 camel_folder_info_free (fi);
1335                         }
1336                         
1337                         need_subscribe = !haveinbox || need_subscribe;
1338                         if (need_subscribe && !just_subscribed && !camel_exception_is_set (ex)) {
1339                                 /* in order to avoid user complaints, force a subscription to INBOX */
1340                                 response = camel_imap_command (store, NULL, ex, "SUBSCRIBE INBOX");
1341                                 if (response != NULL) {
1342                                         /* force a re-check which will pre-fill the summary and
1343                                            also get any folder flags present on the INBOX */
1344                                         camel_imap_response_free (store, response);
1345                                         just_subscribed = TRUE;
1346                                         goto recheck;
1347                                 }
1348                         }
1349                 }
1350                 
1351                 g_ptr_array_free (folders, TRUE);
1352         }
1353         
1354         path = g_strdup_printf ("%s/journal", store->storage_path);
1355         disco_store->diary = camel_disco_diary_new (disco_store, path, ex);
1356         g_free (path);
1357         
1358  done:
1359         /* save any changes we had */
1360         camel_store_summary_save((CamelStoreSummary *)store->summary);
1361
1362         CAMEL_SERVICE_UNLOCK (store, connect_lock);
1363         
1364         if (camel_exception_is_set (ex))
1365                 camel_service_disconnect (service, TRUE, NULL);
1366         else if (camel_disco_diary_empty (disco_store->diary))
1367                 imap_store_refresh_folders (store, ex);
1368         
1369         return !camel_exception_is_set (ex);
1370 }
1371
1372 static gboolean
1373 imap_connect_offline (CamelService *service, CamelException *ex)
1374 {
1375         CamelImapStore *store = CAMEL_IMAP_STORE (service);
1376         CamelDiscoStore *disco_store = CAMEL_DISCO_STORE (service);
1377         char *path;
1378
1379         path = g_strdup_printf ("%s/journal", store->storage_path);
1380         disco_store->diary = camel_disco_diary_new (disco_store, path, ex);
1381         g_free (path);
1382         if (!disco_store->diary)
1383                 return FALSE;
1384         
1385         imap_store_refresh_folders (store, ex);
1386         
1387         store->connected = !camel_exception_is_set (ex);
1388         return store->connected;
1389 }
1390
1391 static gboolean
1392 imap_disconnect_offline (CamelService *service, gboolean clean, CamelException *ex)
1393 {
1394         CamelImapStore *store = CAMEL_IMAP_STORE (service);
1395         CamelDiscoStore *disco = CAMEL_DISCO_STORE (service);
1396         
1397         store->connected = FALSE;
1398         if (store->current_folder) {
1399                 camel_object_unref (CAMEL_OBJECT (store->current_folder));
1400                 store->current_folder = NULL;
1401         }
1402         
1403         if (store->authtypes) {
1404                 g_hash_table_foreach_remove (store->authtypes,
1405                                              free_key, NULL);
1406                 g_hash_table_destroy (store->authtypes);
1407                 store->authtypes = NULL;
1408         }
1409         
1410         if (store->namespace && !(store->parameters & IMAP_PARAM_OVERRIDE_NAMESPACE)) {
1411                 g_free (store->namespace);
1412                 store->namespace = NULL;
1413         }
1414         
1415         if (disco->diary) {
1416                 camel_object_unref (CAMEL_OBJECT (disco->diary));
1417                 disco->diary = NULL;
1418         }
1419         
1420         return TRUE;
1421 }
1422
1423 static gboolean
1424 imap_disconnect_online (CamelService *service, gboolean clean, CamelException *ex)
1425 {
1426         CamelImapStore *store = CAMEL_IMAP_STORE (service);
1427         CamelImapResponse *response;
1428         
1429         if (store->connected && clean) {
1430                 response = camel_imap_command (store, NULL, NULL, "LOGOUT");
1431                 camel_imap_response_free (store, response);
1432         }
1433         
1434         if (store->istream) {
1435                 camel_object_unref (CAMEL_OBJECT (store->istream));
1436                 store->istream = NULL;
1437         }
1438         
1439         if (store->ostream) {
1440                 camel_object_unref (CAMEL_OBJECT (store->ostream));
1441                 store->ostream = NULL;
1442         }
1443         
1444         imap_disconnect_offline (service, clean, ex);
1445         
1446         return TRUE;
1447 }
1448
1449
1450 static gboolean
1451 imap_summary_is_dirty (CamelFolderSummary *summary)
1452 {
1453         CamelMessageInfo *info;
1454         int max, i;
1455         
1456         max = camel_folder_summary_count (summary);
1457         for (i = 0; i < max; i++) {
1458                 info = camel_folder_summary_index (summary, i);
1459                 if (info && (info->flags & CAMEL_MESSAGE_FOLDER_FLAGGED))
1460                         return TRUE;
1461         }
1462         
1463         return FALSE;
1464 }
1465
1466 static void
1467 imap_noop (CamelStore *store, CamelException *ex)
1468 {
1469         CamelImapStore *imap_store = (CamelImapStore *) store;
1470         CamelDiscoStore *disco = (CamelDiscoStore *) store;
1471         CamelImapResponse *response;
1472         CamelFolder *current_folder;
1473         
1474         if (camel_disco_store_status (disco) != CAMEL_DISCO_STORE_ONLINE)
1475                 return;
1476         
1477         CAMEL_SERVICE_LOCK (imap_store, connect_lock);
1478         
1479         current_folder = imap_store->current_folder;
1480         if (current_folder && imap_summary_is_dirty (current_folder->summary)) {
1481                 /* let's sync the flags instead */
1482                 camel_folder_sync (current_folder, FALSE, ex);
1483         } else {
1484                 response = camel_imap_command (imap_store, NULL, ex, "NOOP");
1485                 if (response)
1486                         camel_imap_response_free (imap_store, response);
1487         }
1488         
1489         CAMEL_SERVICE_UNLOCK (imap_store, connect_lock);
1490 }
1491
1492 static guint
1493 hash_folder_name (gconstpointer key)
1494 {
1495         if (strcasecmp (key, "INBOX") == 0)
1496                 return g_str_hash ("INBOX");
1497         else
1498                 return g_str_hash (key);
1499 }
1500
1501 static gint
1502 compare_folder_name (gconstpointer a, gconstpointer b)
1503 {
1504         gconstpointer aname = a, bname = b;
1505
1506         if (strcasecmp (a, "INBOX") == 0)
1507                 aname = "INBOX";
1508         if (strcasecmp (b, "INBOX") == 0)
1509                 bname = "INBOX";
1510         return g_str_equal (aname, bname);
1511 }
1512
1513 static CamelFolder *
1514 no_such_folder (const char *name, CamelException *ex)
1515 {
1516         camel_exception_setv (ex, CAMEL_EXCEPTION_STORE_NO_FOLDER,
1517                               _("No such folder %s"), name);
1518         return NULL;
1519 }
1520
1521 static int
1522 get_folder_status (CamelImapStore *imap_store, const char *folder_name, const char *type)
1523 {
1524         CamelImapResponse *response;
1525         char *status, *p;
1526         int out;
1527
1528         /* FIXME: we assume the server is STATUS-capable */
1529
1530         response = camel_imap_command (imap_store, NULL, NULL,
1531                                        "STATUS %F (%s)",
1532                                        folder_name,
1533                                        type);
1534
1535         if (!response) {
1536                 CamelException ex;
1537
1538                 camel_exception_init (&ex);
1539                 if (imap_check_folder_still_extant (imap_store, folder_name, &ex) == FALSE) {
1540                         imap_folder_effectively_unsubscribed (imap_store, folder_name, &ex);
1541                         imap_forget_folder (imap_store, folder_name, &ex);
1542                 }
1543                 camel_exception_clear (&ex);
1544                 return -1;
1545         }
1546
1547         status = camel_imap_response_extract (imap_store, response,
1548                                               "STATUS", NULL);
1549         if (!status)
1550                 return -1;
1551
1552         p = camel_strstrcase (status, type);
1553         if (p)
1554                 out = strtoul (p + strlen (type), NULL, 10);
1555         else
1556                 out = -1;
1557
1558         g_free (status);
1559         return out;
1560 }
1561
1562 static CamelFolder *
1563 get_folder_online (CamelStore *store, const char *folder_name,
1564                    guint32 flags, CamelException *ex)
1565 {
1566         CamelImapStore *imap_store = CAMEL_IMAP_STORE (store);
1567         CamelImapResponse *response;
1568         CamelFolder *new_folder;
1569         char *folder_dir, *storage_path;
1570         
1571         if (!camel_imap_store_connected (imap_store, ex))
1572                 return NULL;
1573         
1574         if (!strcasecmp (folder_name, "INBOX"))
1575                 folder_name = "INBOX";
1576
1577         /* Lock around the whole lot to check/create atomically */
1578         CAMEL_SERVICE_LOCK (imap_store, connect_lock);
1579         if (imap_store->current_folder) {
1580                 camel_object_unref (CAMEL_OBJECT (imap_store->current_folder));
1581                 imap_store->current_folder = NULL;
1582         }
1583         response = camel_imap_command (imap_store, NULL, NULL, "SELECT %F", folder_name);
1584         if (!response) {
1585                 char *folder_real;
1586
1587                 if (!flags & CAMEL_STORE_FOLDER_CREATE) {
1588                         CAMEL_SERVICE_UNLOCK (imap_store, connect_lock);
1589                         return no_such_folder (folder_name, ex);
1590                 }
1591
1592                 folder_real = camel_imap_store_summary_path_to_full(imap_store->summary, folder_name, store->dir_sep);
1593
1594                 response = camel_imap_command (imap_store, NULL, ex, "CREATE %S", folder_real);
1595
1596                 if (response) {
1597                         camel_imap_store_summary_add_from_full(imap_store->summary, folder_real, store->dir_sep);
1598
1599                         camel_imap_response_free (imap_store, response);
1600                         
1601                         response = camel_imap_command (imap_store, NULL, NULL, "SELECT %F", folder_name);
1602                 }
1603                 g_free(folder_real);
1604                 if (!response) {
1605                         CAMEL_SERVICE_UNLOCK (imap_store, connect_lock);
1606                         return NULL;
1607                 }
1608         }
1609
1610         storage_path = g_strdup_printf("%s/folders", imap_store->storage_path);
1611         folder_dir = e_path_to_physical (storage_path, folder_name);
1612         g_free(storage_path);
1613         new_folder = camel_imap_folder_new (store, folder_name, folder_dir, ex);
1614         g_free (folder_dir);
1615         if (new_folder) {
1616                 CamelException local_ex;
1617
1618                 imap_store->current_folder = new_folder;
1619                 camel_object_ref (CAMEL_OBJECT (new_folder));
1620                 camel_exception_init (&local_ex);
1621                 camel_imap_folder_selected (new_folder, response, &local_ex);
1622
1623                 if (camel_exception_is_set (&local_ex)) {
1624                         camel_exception_xfer (ex, &local_ex);
1625                         camel_object_unref (CAMEL_OBJECT (imap_store->current_folder));
1626                         imap_store->current_folder = NULL;
1627                         camel_object_unref (CAMEL_OBJECT (new_folder));
1628                         new_folder = NULL;
1629                 }
1630         }
1631         camel_imap_response_free_without_processing (imap_store, response);
1632         
1633         CAMEL_SERVICE_UNLOCK (imap_store, connect_lock);
1634         
1635         return new_folder;
1636 }
1637
1638 static CamelFolder *
1639 get_folder_offline (CamelStore *store, const char *folder_name,
1640                     guint32 flags, CamelException *ex)
1641 {
1642         CamelImapStore *imap_store = CAMEL_IMAP_STORE (store);
1643         CamelFolder *new_folder;
1644         char *folder_dir, *storage_path;
1645         
1646         if (!imap_store->connected &&
1647             !camel_service_connect (CAMEL_SERVICE (store), ex))
1648                 return NULL;
1649         
1650         if (!strcasecmp (folder_name, "INBOX"))
1651                 folder_name = "INBOX";
1652         
1653         storage_path = g_strdup_printf("%s/folders", imap_store->storage_path);
1654         folder_dir = e_path_to_physical (storage_path, folder_name);
1655         g_free(storage_path);
1656         if (!folder_dir || access (folder_dir, F_OK) != 0) {
1657                 g_free (folder_dir);
1658                 camel_exception_setv (ex, CAMEL_EXCEPTION_STORE_NO_FOLDER,
1659                                       _("No such folder %s"), folder_name);
1660                 return NULL;
1661         }
1662         
1663         new_folder = camel_imap_folder_new (store, folder_name, folder_dir, ex);
1664         g_free (folder_dir);
1665         
1666         return new_folder;
1667 }
1668
1669 static void
1670 delete_folder (CamelStore *store, const char *folder_name, CamelException *ex)
1671 {
1672         CamelImapStore *imap_store = CAMEL_IMAP_STORE (store);
1673         CamelImapResponse *response;
1674         
1675         if (!camel_disco_store_check_online (CAMEL_DISCO_STORE (store), ex))
1676                 return;
1677         
1678         /* make sure this folder isn't currently SELECTed */
1679         response = camel_imap_command (imap_store, NULL, ex, "SELECT INBOX");
1680         if (response) {
1681                 camel_imap_response_free_without_processing (imap_store, response);
1682                 
1683                 CAMEL_SERVICE_LOCK (imap_store, connect_lock);
1684                 
1685                 if (imap_store->current_folder)
1686                         camel_object_unref (CAMEL_OBJECT (imap_store->current_folder));
1687                 /* no need to actually create a CamelFolder for INBOX */
1688                 imap_store->current_folder = NULL;
1689                 
1690                 CAMEL_SERVICE_UNLOCK (imap_store, connect_lock);
1691         } else
1692                 return;
1693         
1694         response = camel_imap_command (imap_store, NULL, ex, "DELETE %F",
1695                                        folder_name);
1696         
1697         if (response) {
1698                 camel_imap_response_free (imap_store, response);
1699                 imap_forget_folder (imap_store, folder_name, ex);
1700         }
1701 }
1702
1703 static void
1704 manage_subscriptions (CamelStore *store, const char *old_name, gboolean subscribe)
1705 {
1706         CamelImapStore *imap_store = CAMEL_IMAP_STORE (store);
1707         CamelStoreInfo *si;
1708         int olen = strlen(old_name);
1709         const char *path;
1710         int i, count;
1711
1712         count = camel_store_summary_count((CamelStoreSummary *)imap_store->summary);
1713         for (i=0;i<count;i++) {
1714                 si = camel_store_summary_index((CamelStoreSummary *)imap_store->summary, i);
1715                 if (si) {
1716                         path = camel_store_info_path(imap_store->summary, si);
1717                         if (strncmp(path, old_name, olen) == 0) {
1718                                 if (subscribe)
1719                                         subscribe_folder(store, path, NULL);
1720                                 else
1721                                         unsubscribe_folder(store, path, NULL);
1722                         }
1723                         camel_store_summary_info_free((CamelStoreSummary *)imap_store->summary, si);
1724                 }
1725         }
1726 }
1727
1728 static void
1729 rename_folder_info (CamelImapStore *imap_store, const char *old_name, const char *new_name)
1730 {
1731         int i, count;
1732         CamelStoreInfo *si;
1733         int olen = strlen(old_name);
1734         const char *path;
1735         char *npath, *nfull;
1736
1737         count = camel_store_summary_count((CamelStoreSummary *)imap_store->summary);
1738         for (i=0;i<count;i++) {
1739                 si = camel_store_summary_index((CamelStoreSummary *)imap_store->summary, i);
1740                 if (si == NULL)
1741                         continue;
1742                 path = camel_store_info_path(imap_store->summary, si);
1743                 if (strncmp(path, old_name, olen) == 0) {
1744                         if (strlen(path) > olen)
1745                                 npath = g_strdup_printf("%s/%s", new_name, path+olen+1);
1746                         else
1747                                 npath = g_strdup(new_name);
1748                         nfull = camel_imap_store_summary_path_to_full(imap_store->summary, npath, imap_store->dir_sep);
1749                         
1750                         /* workaround for broken server (courier uses '.') that doesn't rename
1751                            subordinate folders as required by rfc 2060 */
1752                         if (imap_store->dir_sep == '.') {
1753                                 CamelImapResponse *response;
1754
1755                                 response = camel_imap_command (imap_store, NULL, NULL, "RENAME %F %S", path, nfull);
1756                                 if (response)
1757                                         camel_imap_response_free (imap_store, response);
1758                         }
1759
1760                         camel_store_info_set_string((CamelStoreSummary *)imap_store->summary, si, CAMEL_STORE_INFO_PATH, npath);
1761                         camel_store_info_set_string((CamelStoreSummary *)imap_store->summary, si, CAMEL_IMAP_STORE_INFO_FULL_NAME, nfull);
1762
1763                         camel_store_summary_touch((CamelStoreSummary *)imap_store->summary);
1764                         g_free(nfull);
1765                         g_free(npath);
1766                 }
1767                 camel_store_summary_info_free((CamelStoreSummary *)imap_store->summary, si);
1768         }
1769 }
1770
1771 static void
1772 rename_folder (CamelStore *store, const char *old_name, const char *new_name_in, CamelException *ex)
1773 {
1774         CamelImapStore *imap_store = CAMEL_IMAP_STORE (store);
1775         CamelImapResponse *response;
1776         char *oldpath, *newpath, *storage_path, *new_name;
1777         
1778         if (!camel_disco_store_check_online (CAMEL_DISCO_STORE (store), ex))
1779                 return;
1780         
1781         /* make sure this folder isn't currently SELECTed - it's
1782            actually possible to rename INBOX but if you do another
1783            INBOX will immediately be created by the server */
1784         response = camel_imap_command (imap_store, NULL, ex, "SELECT INBOX");
1785         if (response) {
1786                 camel_imap_response_free_without_processing (imap_store, response);
1787                 
1788                 CAMEL_SERVICE_LOCK (imap_store, connect_lock);
1789                 
1790                 if (imap_store->current_folder)
1791                         camel_object_unref (CAMEL_OBJECT (imap_store->current_folder));
1792                 /* no need to actually create a CamelFolder for INBOX */
1793                 imap_store->current_folder = NULL;
1794                 
1795                 CAMEL_SERVICE_UNLOCK (imap_store, connect_lock);
1796         } else
1797                 return;
1798         
1799         imap_store->renaming = TRUE;
1800         
1801         if (store->flags & CAMEL_STORE_SUBSCRIPTIONS)
1802                 manage_subscriptions(store, old_name, FALSE);
1803
1804         new_name = camel_imap_store_summary_path_to_full(imap_store->summary, new_name_in, store->dir_sep);
1805         response = camel_imap_command (imap_store, NULL, ex, "RENAME %F %S", old_name, new_name);
1806         
1807         if (!response) {
1808                 if (store->flags & CAMEL_STORE_SUBSCRIPTIONS)
1809                         manage_subscriptions(store, old_name, TRUE);
1810                 g_free(new_name);
1811                 imap_store->renaming = FALSE;
1812                 return;
1813         }
1814         
1815         camel_imap_response_free (imap_store, response);
1816
1817         /* rename summary, and handle broken server */
1818         rename_folder_info(imap_store, old_name, new_name_in);
1819
1820         if (store->flags & CAMEL_STORE_SUBSCRIPTIONS)
1821                 manage_subscriptions(store, new_name_in, TRUE);
1822
1823         storage_path = g_strdup_printf("%s/folders", imap_store->storage_path);
1824         oldpath = e_path_to_physical (storage_path, old_name);
1825         newpath = e_path_to_physical (storage_path, new_name_in);
1826         g_free(storage_path);
1827
1828         /* So do we care if this didn't work?  Its just a cache? */
1829         if (rename (oldpath, newpath) == -1) {
1830                 g_warning ("Could not rename message cache '%s' to '%s': %s: cache reset",
1831                            oldpath, newpath, strerror (errno));
1832         }
1833         
1834         g_free (oldpath);
1835         g_free (newpath);
1836         g_free(new_name);
1837
1838         imap_store->renaming = FALSE;
1839 }
1840
1841 static CamelFolderInfo *
1842 create_folder (CamelStore *store, const char *parent_name,
1843                const char *folder_name, CamelException *ex)
1844 {
1845         CamelImapStore *imap_store = CAMEL_IMAP_STORE (store);
1846         char *full_name, *resp, *thisone, *parent_real, *real_name;
1847         CamelImapResponse *response;
1848         CamelException internal_ex;
1849         CamelFolderInfo *root = NULL;
1850         gboolean need_convert;
1851         int i = 0, flags;
1852         
1853         if (!camel_disco_store_check_online (CAMEL_DISCO_STORE (store), ex))
1854                 return NULL;
1855         if (!parent_name)
1856                 parent_name = "";
1857         
1858         if (strchr (folder_name, imap_store->dir_sep)) {
1859                 camel_exception_setv (ex, CAMEL_EXCEPTION_FOLDER_INVALID_PATH,
1860                                       _("The folder name \"%s\" is invalid because "
1861                                         "it containes the character \"%c\""),
1862                                       folder_name, imap_store->dir_sep);
1863                 return NULL;
1864         }
1865         
1866         /* check if the parent allows inferiors */
1867
1868         /* FIXME: use storesummary directly */
1869         parent_real = camel_imap_store_summary_full_from_path(imap_store->summary, parent_name);
1870         if (parent_real == NULL) {
1871                 camel_exception_setv(ex, CAMEL_EXCEPTION_FOLDER_INVALID_STATE,
1872                                      _("Unknown parent folder: %s"), parent_name);
1873                 return NULL;
1874         }
1875
1876         need_convert = FALSE;
1877         response = camel_imap_command (imap_store, NULL, ex, "LIST \"\" %S",
1878                                        parent_real);
1879         if (!response) /* whoa, this is bad */ {
1880                 g_free(parent_real);
1881                 return NULL;
1882         }
1883         
1884         /* FIXME: does not handle unexpected circumstances very well */
1885         for (i = 0; i < response->untagged->len; i++) {
1886                 resp = response->untagged->pdata[i];
1887                 
1888                 if (!imap_parse_list_response (imap_store, resp, &flags, NULL, &thisone))
1889                         continue;
1890                 
1891                 if (strcmp (thisone, parent_name) == 0) {
1892                         if (flags & CAMEL_FOLDER_NOINFERIORS)
1893                                 need_convert = TRUE;
1894                         break;
1895                 }
1896         }
1897         
1898         camel_imap_response_free (imap_store, response);
1899         
1900         camel_exception_init (&internal_ex);
1901         
1902         /* if not, check if we can delete it and recreate it */
1903         if (need_convert) {
1904                 char *name;
1905                 
1906                 if (get_folder_status (imap_store, parent_name, "MESSAGES")) {
1907                         camel_exception_set (ex, CAMEL_EXCEPTION_FOLDER_INVALID_STATE,
1908                                              _("The parent folder is not allowed to contain subfolders"));
1909                         g_free(parent_real);
1910                         return NULL;
1911                 }
1912                 
1913                 /* delete the old parent and recreate it */
1914                 delete_folder (store, parent_name, &internal_ex);
1915                 if (camel_exception_is_set (&internal_ex)) {
1916                         camel_exception_xfer (ex, &internal_ex);
1917                         return NULL;
1918                 }
1919                 
1920                 /* add the dirsep to the end of parent_name */
1921                 name = g_strdup_printf ("%s%c", parent_real, imap_store->dir_sep);
1922                 response = camel_imap_command (imap_store, NULL, ex, "CREATE %S",
1923                                                name);
1924                 g_free (name);
1925                 
1926                 if (!response) {
1927                         g_free(parent_real);
1928                         return NULL;
1929                 } else
1930                         camel_imap_response_free (imap_store, response);
1931
1932                 root = imap_build_folder_info(imap_store, parent_name);
1933         }
1934         
1935         /* ok now we can create the folder */
1936         real_name = camel_imap_store_summary_path_to_full(imap_store->summary, folder_name, store->dir_sep);
1937         full_name = imap_concat (imap_store, parent_real, real_name);
1938         g_free(real_name);
1939         response = camel_imap_command (imap_store, NULL, ex, "CREATE %S", full_name);
1940         
1941         if (response) {
1942                 CamelImapStoreInfo *si;
1943                 CamelFolderInfo *fi;
1944
1945                 camel_imap_response_free (imap_store, response);
1946
1947                 si = camel_imap_store_summary_add_from_full(imap_store->summary, full_name, store->dir_sep);
1948                 camel_store_summary_save((CamelStoreSummary *)imap_store->summary);
1949                 fi = imap_build_folder_info(imap_store, camel_store_info_path(imap_store->summary, si));
1950                 if (root) {
1951                         root->child = fi;
1952                         fi->parent = root;
1953                 } else {
1954                         root = fi;
1955                 }
1956                 camel_object_trigger_event (CAMEL_OBJECT (store), "folder_created", root);
1957         } else if (root) {
1958                 /* need to re-recreate the folder we just deleted */
1959                 camel_object_trigger_event (CAMEL_OBJECT (store), "folder_created", root);
1960                 camel_folder_info_free(root);
1961                 root = NULL;
1962         }
1963
1964         g_free (full_name);
1965         g_free(parent_real);
1966         
1967         return root;
1968 }
1969
1970 static CamelFolderInfo *
1971 parse_list_response_as_folder_info (CamelImapStore *imap_store,
1972                                     const char *response)
1973 {
1974         CamelFolderInfo *fi;
1975         int flags;
1976         char sep, *dir;
1977         CamelURL *url;
1978         CamelImapStoreInfo *si;
1979         guint32 newflags;
1980
1981         if (!imap_parse_list_response (imap_store, response, &flags, &sep, &dir))
1982                 return NULL;
1983
1984         /* FIXME: should use imap_build_folder_info, note the differences with param setting tho */
1985
1986         si = camel_imap_store_summary_add_from_full(imap_store->summary, dir, sep?sep:'/');
1987         newflags = (si->info.flags & CAMEL_STORE_INFO_FOLDER_SUBSCRIBED) | (flags & ~CAMEL_STORE_INFO_FOLDER_SUBSCRIBED);
1988         if (si->info.flags != newflags) {
1989                 si->info.flags = newflags;
1990                 camel_store_summary_touch((CamelStoreSummary *)imap_store->summary);
1991         }
1992         
1993         fi = g_new0 (CamelFolderInfo, 1);
1994         fi->flags = flags;
1995         fi->name = g_strdup(camel_store_info_name(imap_store->summary, si));
1996         fi->path = g_strdup_printf("/%s", camel_store_info_path(imap_store->summary, si));
1997         fi->full_name = g_strdup(fi->path+1);
1998         
1999         url = camel_url_new (imap_store->base_url, NULL);
2000         camel_url_set_path(url, fi->path);
2001
2002         if (flags & CAMEL_FOLDER_NOSELECT || fi->name[0] == 0)
2003                 camel_url_set_param (url, "noselect", "yes");
2004         fi->url = camel_url_to_string (url, 0);
2005         camel_url_free (url);
2006
2007         /* FIXME: redundant */
2008         if (flags & CAMEL_IMAP_FOLDER_UNMARKED)
2009                 fi->unread_message_count = -1;
2010
2011         return fi;
2012 }
2013
2014 /* this is used when lsub doesn't provide very useful information */
2015 static GPtrArray *
2016 get_subscribed_folders (CamelImapStore *imap_store, const char *top, CamelException *ex)
2017 {
2018         GPtrArray *names, *folders;
2019         int i, toplen = strlen (top);
2020         CamelStoreInfo *si;
2021         CamelImapResponse *response;
2022         CamelFolderInfo *fi;
2023         char *result;
2024         int haveinbox = FALSE;
2025
2026         folders = g_ptr_array_new ();
2027         names = g_ptr_array_new ();
2028         for (i=0;(si = camel_store_summary_index((CamelStoreSummary *)imap_store->summary, i));i++) {
2029                 if (si->flags & CAMEL_STORE_INFO_FOLDER_SUBSCRIBED) {
2030                         g_ptr_array_add(names, (char *)camel_imap_store_info_full_name(imap_store->summary, si));
2031                         haveinbox = haveinbox || strcasecmp(camel_imap_store_info_full_name(imap_store->summary, si), "INBOX") == 0;
2032                 }
2033                 camel_store_summary_info_free((CamelStoreSummary *)imap_store->summary, si);
2034         }
2035         
2036         if (!haveinbox)
2037                 g_ptr_array_add (names, "INBOX");
2038         
2039         for (i = 0; i < names->len; i++) {
2040                 response = camel_imap_command (imap_store, NULL, ex,
2041                                                "LIST \"\" %S",
2042                                                names->pdata[i]);
2043                 if (!response)
2044                         break;
2045                 
2046                 result = camel_imap_response_extract (imap_store, response, "LIST", NULL);
2047                 if (!result) {
2048                         camel_store_summary_remove_path((CamelStoreSummary *)imap_store->summary, names->pdata[i]);
2049                         g_ptr_array_remove_index_fast (names, i);
2050                         i--;
2051                         continue;
2052                 }
2053                 
2054                 fi = parse_list_response_as_folder_info (imap_store, result);
2055                 if (!fi)
2056                         continue;
2057                 
2058                 if (strncmp (top, fi->full_name, toplen) != 0) {
2059                         camel_folder_info_free (fi);
2060                         continue;
2061                 }
2062                 
2063                 g_ptr_array_add (folders, fi);
2064         }
2065         
2066         g_ptr_array_free (names, TRUE);
2067         
2068         return folders;
2069 }
2070
2071 static int imap_match_pattern(char dir_sep, const char *pattern, const char *name)
2072 {
2073         char p, n;
2074
2075         p = *pattern++;
2076         n = *name++;
2077         while (n && p) {
2078                 if (n == p) {
2079                         p = *pattern++;
2080                         n = *name++;
2081                 } else if (p == '%') {
2082                         if (n != dir_sep) {
2083                                 n = *name++;
2084                         } else {
2085                                 p = *pattern++;
2086                         }
2087                 } else if (p == '*') {
2088                         return TRUE;
2089                 } else
2090                         return FALSE;
2091         }
2092
2093         return n == 0 && (p == '%' || p == 0);
2094 }
2095
2096 static void
2097 get_folders_online (CamelImapStore *imap_store, const char *pattern,
2098                     GPtrArray *folders, gboolean lsub, CamelException *ex)
2099 {
2100         CamelImapResponse *response;
2101         CamelFolderInfo *fi;
2102         char *list;
2103         int i, count;
2104         GHashTable *present;
2105         CamelStoreInfo *si;
2106
2107         response = camel_imap_command (imap_store, NULL, ex,
2108                                        "%s \"\" %S", lsub ? "LSUB" : "LIST",
2109                                        pattern);
2110         if (!response)
2111                 return;
2112
2113         present = g_hash_table_new(g_str_hash, g_str_equal);
2114         for (i = 0; i < response->untagged->len; i++) {
2115                 list = response->untagged->pdata[i];
2116                 fi = parse_list_response_as_folder_info (imap_store, list);
2117                 if (fi) {
2118                         g_ptr_array_add(folders, fi);
2119                         g_hash_table_insert(present, fi->full_name, fi);
2120                 }
2121         }
2122         camel_imap_response_free (imap_store, response);
2123
2124         /* update our summary to match the server */
2125         count = camel_store_summary_count((CamelStoreSummary *)imap_store->summary);
2126         for (i=0;i<count;i++) {
2127                 si = camel_store_summary_index((CamelStoreSummary *)imap_store->summary, i);
2128                 if (si == NULL)
2129                         continue;
2130
2131                 if (imap_match_pattern(((CamelStore *)imap_store)->dir_sep, pattern, camel_imap_store_info_full_name(imap_store->summary, si))) {
2132                         if (g_hash_table_lookup(present, camel_store_info_path(imap_store->summary, si)) != NULL) {
2133                                 if (lsub && (si->flags & CAMEL_STORE_INFO_FOLDER_SUBSCRIBED) == 0) {
2134                                         si->flags |= CAMEL_STORE_INFO_FOLDER_SUBSCRIBED;
2135                                         camel_store_summary_touch((CamelStoreSummary *)imap_store->summary);
2136                                 }
2137                         } else {
2138                                 if (lsub) {
2139                                         if (si->flags & CAMEL_STORE_INFO_FOLDER_SUBSCRIBED) {
2140                                                 si->flags &= ~CAMEL_STORE_INFO_FOLDER_SUBSCRIBED;
2141                                                 camel_store_summary_touch((CamelStoreSummary *)imap_store->summary);
2142                                         }
2143                                 } else {
2144                                         camel_store_summary_remove((CamelStoreSummary *)imap_store->summary, si);
2145                                         count--;
2146                                         i--;
2147                                 }
2148                         }
2149                 }
2150                 camel_store_summary_info_free((CamelStoreSummary *)imap_store->summary, si);
2151         }
2152         g_hash_table_destroy(present);
2153 }
2154
2155 #if 0
2156 static void
2157 dumpfi(CamelFolderInfo *fi)
2158 {
2159         int depth;
2160         CamelFolderInfo *n = fi;
2161
2162         if (fi == NULL)
2163                 return;
2164
2165         depth = 0;
2166         while (n->parent) {
2167                 depth++;
2168                 n = n->parent;
2169         }
2170
2171         while (fi) {
2172                 printf("%-40s %-30s %*s\n", fi->path, fi->full_name, depth*2+strlen(fi->url), fi->url);
2173                 if (fi->child)
2174                         dumpfi(fi->child);
2175                 fi = fi->sibling;
2176         }
2177 }
2178 #endif
2179
2180 static void
2181 get_folder_counts(CamelImapStore *imap_store, CamelFolderInfo *fi, CamelException *ex)
2182 {
2183         GSList *q;
2184         CamelFolder *folder;
2185
2186         /* non-recursive breath first search */
2187
2188         q = g_slist_append(NULL, fi);
2189
2190         while (q) {
2191                 fi = q->data;
2192                 q = g_slist_remove_link(q, q);
2193
2194                 while (fi) {
2195                         /* ignore noselect folders, and check only inbox if we only check inbox */
2196                         if ((fi->flags & CAMEL_FOLDER_NOSELECT) == 0
2197                             && ( (imap_store->parameters & IMAP_PARAM_CHECK_ALL)
2198                                  || strcasecmp(fi->full_name, "inbox") == 0) ) {
2199
2200                                 CAMEL_SERVICE_LOCK (imap_store, connect_lock);
2201                                 /* For the current folder, poke it to check for new     
2202                                  * messages and then report that number, rather than
2203                                  * doing a STATUS command.
2204                                  */
2205                                 if (imap_store->current_folder && strcmp(imap_store->current_folder->full_name, fi->full_name) == 0) {
2206                                         /* we bypass the folder locking otherwise we can deadlock.  we use the command lock for
2207                                            any operations anyway so this is 'safe'.  See comment above imap_store_refresh_folders() for info */
2208                                         CAMEL_FOLDER_CLASS (CAMEL_OBJECT_GET_CLASS(imap_store->current_folder))->refresh_info(imap_store->current_folder, ex);
2209                                         fi->unread_message_count = camel_folder_get_unread_message_count (imap_store->current_folder);
2210                                 } else {
2211                                         fi->unread_message_count = get_folder_status (imap_store, fi->full_name, "UNSEEN");
2212                                         /* if we have this folder open, and the unread count has changed, update */
2213                                         folder = camel_object_bag_get(CAMEL_STORE(imap_store)->folders, fi->full_name);
2214                                         if (folder && fi->unread_message_count != camel_folder_get_unread_message_count(folder)) {
2215                                                 CAMEL_FOLDER_CLASS (CAMEL_OBJECT_GET_CLASS(folder))->refresh_info(folder, ex);
2216                                                 fi->unread_message_count = camel_folder_get_unread_message_count(folder);
2217                                         }
2218                                         if (folder)
2219                                                 camel_object_unref(folder);
2220
2221                                 }
2222                 
2223                                 CAMEL_SERVICE_UNLOCK (imap_store, connect_lock);
2224                         } else {
2225                                 /* since its cheap, get it if they're open */
2226                                 folder = camel_object_bag_get(CAMEL_STORE(imap_store)->folders, fi->full_name);
2227                                 if (folder) {
2228                                         fi->unread_message_count = camel_folder_get_unread_message_count(folder);
2229                                         camel_object_unref(folder);
2230                                 } else
2231                                         fi->unread_message_count = -1;
2232                         }
2233
2234                         if (fi->child)
2235                                 q = g_slist_append(q, fi->child);
2236                         fi = fi->sibling;
2237                 }
2238         }
2239 }
2240
2241 /* imap needs to treat inbox case insensitive */
2242 /* we'll assume the names are normalised already */
2243 static guint folder_hash(const void *ap)
2244 {
2245         const char *a = ap;
2246
2247         if (strcasecmp(a, "INBOX") == 0)
2248                 a = "INBOX";
2249
2250         return g_str_hash(a);
2251 }
2252
2253 static int folder_eq(const void *ap, const void *bp)
2254 {
2255         const char *a = ap;
2256         const char *b = bp;
2257
2258         if (strcasecmp(a, "INBOX") == 0)
2259                 a = "INBOX";
2260         if (strcasecmp(b, "INBOX") == 0)
2261                 b = "INBOX";
2262
2263         return g_str_equal(a, b);
2264 }
2265
2266 static GPtrArray *
2267 get_folders(CamelStore *store, const char *top, guint32 flags, CamelException *ex)
2268 {
2269         CamelImapStore *imap_store = CAMEL_IMAP_STORE (store);
2270         GSList *p = NULL;
2271         GHashTable *infos;
2272         int i;
2273         GPtrArray *folders, *folders_out;
2274         CamelFolderInfo *fi;
2275         char *name;
2276         int depth = 0;
2277         int haveinbox = 0;
2278         static int imap_max_depth = 0;
2279
2280         if (!camel_imap_store_connected (imap_store, ex))
2281                 return NULL;
2282
2283         /* allow megalomaniacs to override the max of 10 */
2284         if (imap_max_depth == 0) {
2285                 name = getenv("CAMEL_IMAP_MAX_DEPTH");
2286                 if (name) {
2287                         imap_max_depth = atoi (name);
2288                         imap_max_depth = MIN (MAX (imap_max_depth, 0), 2);
2289                 } else
2290                         imap_max_depth = 10;
2291         }
2292
2293         infos = g_hash_table_new(folder_hash, folder_eq);
2294
2295         /* get starting point & strip trailing '/' */
2296         if (top[0] == 0) {
2297                 if (imap_store->namespace) {
2298                         top = imap_store->namespace;
2299                         i = strlen(top)-1;
2300                         name = g_malloc(i+2);
2301                         strcpy(name, top);
2302                         while (i>0 && name[i] == store->dir_sep)
2303                                 name[i--] = 0;
2304                 } else
2305                         name = g_strdup("");
2306         } else {
2307                 name = camel_imap_store_summary_full_from_path(imap_store->summary, top);
2308                 if (name == NULL)
2309                         name = camel_imap_store_summary_path_to_full(imap_store->summary, top, store->dir_sep);
2310         }
2311
2312         d(printf("\n\nList '%s' %s\n", name, flags&CAMEL_STORE_FOLDER_INFO_RECURSIVE?"RECURSIVE":"NON-RECURSIVE"));
2313
2314         folders_out = g_ptr_array_new();
2315         folders = g_ptr_array_new();
2316         
2317         /* first get working list of names */
2318         get_folders_online (imap_store, name[0]?name:"%", folders, flags & CAMEL_STORE_FOLDER_INFO_SUBSCRIBED, ex);
2319         if (camel_exception_is_set(ex))
2320                 goto fail;
2321         for (i=0; i<folders->len && !haveinbox; i++) {
2322                 fi = folders->pdata[i];
2323                 haveinbox = (strcasecmp(fi->full_name, "INBOX")) == 0;
2324         }
2325
2326         if (!haveinbox && top == imap_store->namespace) {
2327                 get_folders_online (imap_store, "INBOX", folders,
2328                                     flags & CAMEL_STORE_FOLDER_INFO_SUBSCRIBED, ex);
2329                 
2330                 if (camel_exception_is_set (ex))
2331                         goto fail;
2332         }
2333
2334         for (i=0; i<folders->len; i++)
2335                 p = g_slist_prepend(p, folders->pdata[i]);
2336
2337         g_ptr_array_set_size(folders, 0);
2338
2339         /* p is a reversed list of pending folders for the next level, q is the list of folders for this */
2340         while (p) {
2341                 GSList *q = g_slist_reverse(p);
2342
2343                 p = NULL;
2344                 while (q) {
2345                         fi = q->data;
2346
2347                         q = g_slist_remove_link(q, q);
2348                         g_ptr_array_add(folders_out, fi);
2349
2350                         d(printf("Checking folder '%s'\n", fi->full_name));
2351
2352                         /* First if we're not recursive mode on the top level, and we know it has or doesn't
2353                             or can't have children, no need to go further - a bit ugly */
2354                         if ( top == imap_store->namespace
2355                              && (flags & CAMEL_STORE_FOLDER_INFO_RECURSIVE) == 0
2356                              && (fi->flags & (CAMEL_FOLDER_CHILDREN|CAMEL_IMAP_FOLDER_NOCHILDREN|CAMEL_FOLDER_NOINFERIORS)) != 0) {
2357                                 /* do nothing */
2358                         }
2359                                 /* Otherwise, if this has (or might have) children, scan it */
2360                         else if ( (fi->flags & (CAMEL_IMAP_FOLDER_NOCHILDREN|CAMEL_FOLDER_NOINFERIORS)) == 0
2361                                   || (fi->flags & CAMEL_FOLDER_CHILDREN) != 0) {
2362                                 char *n, *real;
2363
2364                                 real = camel_imap_store_summary_full_from_path(imap_store->summary, fi->full_name);
2365                                 n = imap_concat(imap_store, real?real:fi->full_name, "%");
2366                                 get_folders_online(imap_store, n, folders, flags & CAMEL_STORE_FOLDER_INFO_SUBSCRIBED, ex);
2367                                 g_free(n);
2368                                 g_free(real);
2369
2370                                 if (folders->len > 0)
2371                                         fi->flags |= CAMEL_FOLDER_CHILDREN;
2372
2373                                 for (i=0;i<folders->len;i++) {
2374                                         fi = folders->pdata[i];
2375                                         if (g_hash_table_lookup(infos, fi->full_name) == NULL) {
2376                                                 g_hash_table_insert(infos, fi->full_name, fi);
2377                                                 if ((flags & CAMEL_STORE_FOLDER_INFO_RECURSIVE) && depth<imap_max_depth)
2378                                                         p = g_slist_prepend(p, fi);
2379                                                 else
2380                                                         g_ptr_array_add(folders_out, fi);
2381                                         } else {
2382                                                 camel_folder_info_free(fi);
2383                                         }
2384                                 }
2385                                 g_ptr_array_set_size(folders, 0);
2386                         }
2387                 }
2388                 depth++;
2389         }
2390
2391         g_ptr_array_free(folders, TRUE);
2392         g_hash_table_destroy(infos);
2393         g_free(name);
2394
2395         return folders_out;
2396 fail:
2397         g_ptr_array_free(folders, TRUE);
2398         g_ptr_array_free(folders_out, TRUE);
2399         g_hash_table_destroy(infos);
2400         g_free(name);
2401
2402         return NULL;
2403 }
2404
2405 static CamelFolderInfo *
2406 get_folder_info_online (CamelStore *store, const char *top, guint32 flags, CamelException *ex)
2407 {
2408         CamelImapStore *imap_store = CAMEL_IMAP_STORE (store);
2409         CamelFolderInfo *tree;
2410         GPtrArray *folders;
2411         
2412         if (top == NULL)
2413                 top = "";
2414
2415         if ((flags & CAMEL_STORE_FOLDER_INFO_SUBSCRIBED)
2416             && !(imap_store->capabilities & IMAP_CAPABILITY_useful_lsub)
2417             && (imap_store->parameters & IMAP_PARAM_CHECK_ALL))
2418                 folders = get_subscribed_folders(imap_store, top, ex);
2419         else
2420                 folders = get_folders(store, top, flags, ex);
2421
2422         if (folders == NULL)
2423                 return NULL;
2424         
2425         tree = camel_folder_info_build(folders, top, '/', TRUE);
2426         g_ptr_array_free(folders, TRUE);
2427         
2428         if (!(flags & CAMEL_STORE_FOLDER_INFO_FAST))
2429                 get_folder_counts(imap_store, tree, ex);
2430
2431         d(dumpfi(tree));
2432         camel_store_summary_save((CamelStoreSummary *)imap_store->summary);
2433
2434         return tree;
2435 }
2436
2437 static gboolean
2438 get_one_folder_offline (const char *physical_path, const char *path, gpointer data)
2439 {
2440         GPtrArray *folders = data;
2441         CamelImapStore *imap_store = folders->pdata[0];
2442         CamelFolderInfo *fi;
2443         CamelStoreInfo *si;
2444
2445         if (*path != '/')
2446                 return TRUE;
2447
2448         /* folder_info_build will insert parent nodes as necessary and mark
2449          * them as noselect, which is information we actually don't have at
2450          * the moment. So let it do the right thing by bailing out if it's
2451          * not a folder we're explicitly interested in.
2452          */
2453
2454         si = camel_store_summary_path((CamelStoreSummary *)imap_store->summary, path+1);
2455         if (si) {
2456                 if ((((CamelStore *)imap_store)->flags & CAMEL_STORE_SUBSCRIPTIONS) == 0
2457                     || (si->flags & CAMEL_STORE_INFO_FOLDER_SUBSCRIBED)) {
2458                         fi = imap_build_folder_info(imap_store, path+1);
2459                         fi->flags = si->flags;
2460                         if (si->flags & CAMEL_FOLDER_NOSELECT) {
2461                                 CamelURL *url = camel_url_new(fi->url, NULL);
2462                                 
2463                                 camel_url_set_param (url, "noselect", "yes");
2464                                 g_free(fi->url);
2465                                 fi->url = camel_url_to_string (url, 0);
2466                                 camel_url_free (url);
2467                         }
2468                         g_ptr_array_add (folders, fi);
2469                 }
2470                 camel_store_summary_info_free((CamelStoreSummary *)imap_store->summary, si);
2471         }
2472
2473         return TRUE;
2474 }
2475
2476 static CamelFolderInfo *
2477 get_folder_info_offline (CamelStore *store, const char *top,
2478                          guint32 flags, CamelException *ex)
2479 {
2480         CamelImapStore *imap_store = CAMEL_IMAP_STORE (store);
2481         CamelFolderInfo *fi;
2482         GPtrArray *folders;
2483         char *storage_path;
2484
2485         if (!imap_store->connected &&
2486             !camel_service_connect (CAMEL_SERVICE (store), ex))
2487                 return NULL;
2488
2489         if ((store->flags & CAMEL_STORE_SUBSCRIPTIONS) &&
2490             !(flags & CAMEL_STORE_FOLDER_INFO_SUBSCRIBED)) {
2491                 camel_disco_store_check_online (CAMEL_DISCO_STORE (store), ex);
2492                 return NULL;
2493         }
2494
2495         /* FIXME: obey other flags */
2496
2497         folders = g_ptr_array_new ();
2498
2499         /* A kludge to avoid having to pass a struct to the callback */
2500         g_ptr_array_add (folders, imap_store);
2501         storage_path = g_strdup_printf("%s/folders", imap_store->storage_path);
2502         if (!e_path_find_folders (storage_path, get_one_folder_offline, folders)) {
2503                 camel_disco_store_check_online (CAMEL_DISCO_STORE (imap_store), ex);
2504                 fi = NULL;
2505         } else {
2506                 g_ptr_array_remove_index_fast (folders, 0);
2507                 fi = camel_folder_info_build (folders, "", '/', TRUE);
2508         }
2509         g_free(storage_path);
2510
2511         g_ptr_array_free (folders, TRUE);
2512         return fi;
2513 }
2514
2515 static gboolean
2516 folder_subscribed (CamelStore *store, const char *folder_name)
2517 {
2518         CamelImapStore *imap_store = CAMEL_IMAP_STORE (store);
2519         CamelStoreInfo *si;
2520         int truth = FALSE;
2521
2522         si = camel_store_summary_path((CamelStoreSummary *)imap_store->summary, folder_name);
2523         if (si) {
2524                 truth = (si->flags & CAMEL_STORE_INFO_FOLDER_SUBSCRIBED) != 0;
2525                 camel_store_summary_info_free((CamelStoreSummary *)imap_store->summary, si);
2526         }
2527
2528         return truth;
2529 }
2530
2531 /* Note: folder_name must match a folder as listed with get_folder_info() -> full_name */
2532 static void
2533 subscribe_folder (CamelStore *store, const char *folder_name,
2534                   CamelException *ex)
2535 {
2536         CamelImapStore *imap_store = CAMEL_IMAP_STORE (store);
2537         CamelImapResponse *response;
2538         CamelFolderInfo *fi;
2539         CamelStoreInfo *si;
2540
2541         if (!camel_disco_store_check_online (CAMEL_DISCO_STORE (store), ex))
2542                 return;
2543         if (!camel_imap_store_connected (imap_store, ex))
2544                 return;
2545         
2546         response = camel_imap_command (imap_store, NULL, ex,
2547                                        "SUBSCRIBE %F", folder_name);
2548         if (!response)
2549                 return;
2550         camel_imap_response_free (imap_store, response);
2551         
2552         si = camel_store_summary_path((CamelStoreSummary *)imap_store->summary, folder_name);
2553         if (si) {
2554                 if ((si->flags & CAMEL_STORE_INFO_FOLDER_SUBSCRIBED) == 0) {
2555                         si->flags |= CAMEL_STORE_INFO_FOLDER_SUBSCRIBED;
2556                         camel_store_summary_touch((CamelStoreSummary *)imap_store->summary);
2557                         camel_store_summary_save((CamelStoreSummary *)imap_store->summary);
2558                 }
2559                 camel_store_summary_info_free((CamelStoreSummary *)imap_store->summary, si);
2560         }
2561         
2562         if (imap_store->renaming) {
2563                 /* we don't need to emit a "folder_subscribed" signal
2564                    if we are in the process of renaming folders, so we
2565                    are done here... */
2566                 return;
2567         }
2568
2569         fi = imap_build_folder_info(imap_store, folder_name);
2570         camel_object_trigger_event (CAMEL_OBJECT (store), "folder_subscribed", fi);
2571         camel_folder_info_free (fi);
2572 }
2573
2574 static void
2575 unsubscribe_folder (CamelStore *store, const char *folder_name,
2576                     CamelException *ex)
2577 {
2578         CamelImapStore *imap_store = CAMEL_IMAP_STORE (store);
2579         CamelImapResponse *response;
2580         
2581         if (!camel_disco_store_check_online (CAMEL_DISCO_STORE (store), ex))
2582                 return;
2583         if (!camel_imap_store_connected (imap_store, ex))
2584                 return;
2585         
2586         response = camel_imap_command (imap_store, NULL, ex,
2587                                        "UNSUBSCRIBE %F", folder_name);
2588         if (!response)
2589                 return;
2590         camel_imap_response_free (imap_store, response);
2591
2592         imap_folder_effectively_unsubscribed (imap_store, folder_name, ex);
2593 }
2594
2595 #if 0
2596 static gboolean
2597 folder_flags_have_changed (CamelFolder *folder)
2598 {
2599         CamelMessageInfo *info;
2600         int i, max;
2601         
2602         max = camel_folder_summary_count (folder->summary);
2603         for (i = 0; i < max; i++) {
2604                 info = camel_folder_summary_index (folder->summary, i);
2605                 if (!info)
2606                         continue;
2607                 if (info->flags & CAMEL_MESSAGE_FOLDER_FLAGGED) {
2608                         return TRUE;
2609                 }
2610         }
2611         
2612         return FALSE;
2613 }
2614 #endif
2615
2616
2617 gboolean
2618 camel_imap_store_connected (CamelImapStore *store, CamelException *ex)
2619 {
2620         if (store->istream == NULL || !store->connected)
2621                 return camel_service_connect (CAMEL_SERVICE (store), ex);
2622         return TRUE;
2623 }
2624
2625
2626 /* FIXME: please god, when will the hurting stop? Thus function is so
2627    fucking broken it's not even funny. */
2628 ssize_t
2629 camel_imap_store_readline (CamelImapStore *store, char **dest, CamelException *ex)
2630 {
2631         CamelStreamBuffer *stream;
2632         char linebuf[1024];
2633         GByteArray *ba;
2634         ssize_t nread;
2635         
2636         g_return_val_if_fail (CAMEL_IS_IMAP_STORE (store), -1);
2637         g_return_val_if_fail (dest, -1);
2638         
2639         *dest = NULL;
2640         
2641         /* Check for connectedness. Failed (or cancelled) operations will
2642          * close the connection. We can't expect a read to have any
2643          * meaning if we reconnect, so always set an exception.
2644          */
2645         
2646         if (!camel_imap_store_connected (store, ex)) {
2647                 camel_exception_set (ex, CAMEL_EXCEPTION_SERVICE_NOT_CONNECTED,
2648                                      g_strerror (errno));
2649                 return -1;
2650         }
2651         
2652         stream = CAMEL_STREAM_BUFFER (store->istream);
2653         
2654         ba = g_byte_array_new ();
2655         while ((nread = camel_stream_buffer_gets (stream, linebuf, sizeof (linebuf))) > 0) {
2656                 g_byte_array_append (ba, linebuf, nread);
2657                 if (linebuf[nread - 1] == '\n')
2658                         break;
2659         }
2660         
2661         if (nread <= 0) {
2662                 if (errno == EINTR)
2663                         camel_exception_set (ex, CAMEL_EXCEPTION_USER_CANCEL, _("Operation cancelled"));
2664                 else
2665                         camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
2666                                               _("Server unexpectedly disconnected: %s"),
2667                                               g_strerror (errno));
2668                 
2669                 camel_service_disconnect (CAMEL_SERVICE (store), FALSE, NULL);
2670                 g_byte_array_free (ba, TRUE);
2671                 return -1;
2672         }
2673         
2674         if (camel_verbose_debug) {
2675                 fprintf (stderr, "received: ");
2676                 fwrite (ba->data, 1, ba->len, stderr);
2677         }
2678         
2679         /* camel-imap-command.c:imap_read_untagged expects the CRLFs
2680            to be stripped off and be nul-terminated *sigh* */
2681         nread = ba->len - 1;
2682         ba->data[nread] = '\0';
2683         if (ba->data[nread - 1] == '\r') {
2684                 ba->data[nread - 1] = '\0';
2685                 nread--;
2686         }
2687         
2688         *dest = ba->data;
2689         g_byte_array_free (ba, FALSE);
2690         
2691         return nread;
2692 }