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