1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /* camel-imap-store.c : class for an imap store */
6 * Dan Winship <danw@ximian.com>
7 * Jeffrey Stedfast <fejj@ximian.com>
9 * Copyright 2000, 2001 Ximian, Inc.
11 * This program is free software; you can redistribute it and/or
12 * modify it under the terms of version 2 of the GNU General Public
13 * License as published by the Free Software Foundation.
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.
20 * You should have received a copy of the GNU General Public
21 * License along with this program; if not, write to the
22 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
23 * Boston, MA 02111-1307, USA.
38 #include "e-util/e-path.h"
40 #include "camel-imap-store.h"
41 #include "camel-imap-store-summary.h"
42 #include "camel-imap-folder.h"
43 #include "camel-imap-utils.h"
44 #include "camel-imap-command.h"
45 #include "camel-imap-summary.h"
46 #include "camel-imap-message-cache.h"
47 #include "camel-disco-diary.h"
48 #include "camel-file-utils.h"
49 #include "camel-folder.h"
50 #include "camel-exception.h"
51 #include "camel-session.h"
52 #include "camel-stream.h"
53 #include "camel-stream-buffer.h"
54 #include "camel-stream-fs.h"
55 #include "camel-tcp-stream-raw.h"
56 #include "camel-tcp-stream-ssl.h"
57 #include "camel-url.h"
58 #include "camel-sasl.h"
59 #include "camel-utf8.h"
60 #include "camel-string-utils.h"
62 #include "camel-imap-private.h"
63 #include "camel-private.h"
67 /* Specified in RFC 2060 */
69 #define SIMAP_PORT 993
72 extern int camel_verbose_debug;
74 static CamelDiscoStoreClass *parent_class = NULL;
76 static char imap_tag_prefix = 'A';
78 static void construct (CamelService *service, CamelSession *session,
79 CamelProvider *provider, CamelURL *url,
82 static int imap_setv (CamelObject *object, CamelException *ex, CamelArgV *args);
83 static int imap_getv (CamelObject *object, CamelException *ex, CamelArgGetV *args);
85 static char *imap_get_name (CamelService *service, gboolean brief);
87 static gboolean can_work_offline (CamelDiscoStore *disco_store);
88 static gboolean imap_connect_online (CamelService *service, CamelException *ex);
89 static gboolean imap_connect_offline (CamelService *service, CamelException *ex);
90 static gboolean imap_disconnect_online (CamelService *service, gboolean clean, CamelException *ex);
91 static gboolean imap_disconnect_offline (CamelService *service, gboolean clean, CamelException *ex);
92 static void imap_noop (CamelStore *store, CamelException *ex);
93 static GList *query_auth_types (CamelService *service, CamelException *ex);
94 static guint hash_folder_name (gconstpointer key);
95 static gint compare_folder_name (gconstpointer a, gconstpointer b);
96 static CamelFolder *get_folder_online (CamelStore *store, const char *folder_name, guint32 flags, CamelException *ex);
97 static CamelFolder *get_folder_offline (CamelStore *store, const char *folder_name, guint32 flags, CamelException *ex);
98 static CamelFolderInfo *create_folder (CamelStore *store, const char *parent_name, const char *folder_name, CamelException *ex);
99 static void delete_folder (CamelStore *store, const char *folder_name, CamelException *ex);
100 static void rename_folder (CamelStore *store, const char *old_name, const char *new_name, CamelException *ex);
101 static CamelFolderInfo *get_folder_info_online (CamelStore *store,
105 static CamelFolderInfo *get_folder_info_offline (CamelStore *store,
109 static gboolean folder_subscribed (CamelStore *store, const char *folder_name);
110 static void subscribe_folder (CamelStore *store, const char *folder_name,
112 static void unsubscribe_folder (CamelStore *store, const char *folder_name,
115 static void get_folders_online (CamelImapStore *imap_store, const char *pattern,
116 GPtrArray *folders, gboolean lsub, CamelException *ex);
119 static void imap_folder_effectively_unsubscribed(CamelImapStore *imap_store, const char *folder_name, CamelException *ex);
120 static gboolean imap_check_folder_still_extant (CamelImapStore *imap_store, const char *full_name, CamelException *ex);
121 static void imap_forget_folder(CamelImapStore *imap_store, const char *folder_name, CamelException *ex);
122 static void imap_set_server_level (CamelImapStore *store);
125 camel_imap_store_class_init (CamelImapStoreClass *camel_imap_store_class)
127 CamelObjectClass *camel_object_class =
128 CAMEL_OBJECT_CLASS (camel_imap_store_class);
129 CamelServiceClass *camel_service_class =
130 CAMEL_SERVICE_CLASS (camel_imap_store_class);
131 CamelStoreClass *camel_store_class =
132 CAMEL_STORE_CLASS (camel_imap_store_class);
133 CamelDiscoStoreClass *camel_disco_store_class =
134 CAMEL_DISCO_STORE_CLASS (camel_imap_store_class);
136 parent_class = CAMEL_DISCO_STORE_CLASS (camel_type_get_global_classfuncs (camel_disco_store_get_type ()));
138 /* virtual method overload */
139 camel_object_class->setv = imap_setv;
140 camel_object_class->getv = imap_getv;
142 camel_service_class->construct = construct;
143 camel_service_class->query_auth_types = query_auth_types;
144 camel_service_class->get_name = imap_get_name;
146 camel_store_class->hash_folder_name = hash_folder_name;
147 camel_store_class->compare_folder_name = compare_folder_name;
148 camel_store_class->create_folder = create_folder;
149 camel_store_class->delete_folder = delete_folder;
150 camel_store_class->rename_folder = rename_folder;
151 camel_store_class->free_folder_info = camel_store_free_folder_info_full;
152 camel_store_class->folder_subscribed = folder_subscribed;
153 camel_store_class->subscribe_folder = subscribe_folder;
154 camel_store_class->unsubscribe_folder = unsubscribe_folder;
155 camel_store_class->noop = imap_noop;
157 camel_disco_store_class->can_work_offline = can_work_offline;
158 camel_disco_store_class->connect_online = imap_connect_online;
159 camel_disco_store_class->connect_offline = imap_connect_offline;
160 camel_disco_store_class->disconnect_online = imap_disconnect_online;
161 camel_disco_store_class->disconnect_offline = imap_disconnect_offline;
162 camel_disco_store_class->get_folder_online = get_folder_online;
163 camel_disco_store_class->get_folder_offline = get_folder_offline;
164 camel_disco_store_class->get_folder_resyncing = get_folder_online;
165 camel_disco_store_class->get_folder_info_online = get_folder_info_online;
166 camel_disco_store_class->get_folder_info_offline = get_folder_info_offline;
167 camel_disco_store_class->get_folder_info_resyncing = get_folder_info_online;
171 free_key (gpointer key, gpointer value, gpointer user_data)
178 camel_imap_store_finalize (CamelObject *object)
180 CamelImapStore *imap_store = CAMEL_IMAP_STORE (object);
182 /* This frees current_folder, folders, authtypes, streams, and namespace. */
183 camel_service_disconnect((CamelService *)imap_store, TRUE, NULL);
185 if (imap_store->summary) {
186 camel_store_summary_save((CamelStoreSummary *)imap_store->summary);
187 camel_object_unref(imap_store->summary);
190 if (imap_store->base_url)
191 g_free (imap_store->base_url);
192 if (imap_store->storage_path)
193 g_free (imap_store->storage_path);
195 #ifdef ENABLE_THREADS
196 e_thread_destroy (imap_store->async_thread);
200 #ifdef ENABLE_THREADS
201 static void async_destroy(EThread *et, EMsg *em, void *data)
203 CamelImapStore *imap_store = data;
204 CamelImapMsg *msg = (CamelImapMsg *)em;
207 msg->free (imap_store, msg);
212 static void async_received(EThread *et, EMsg *em, void *data)
214 CamelImapStore *imap_store = data;
215 CamelImapMsg *msg = (CamelImapMsg *)em;
218 msg->receive(imap_store, msg);
221 CamelImapMsg *camel_imap_msg_new(void (*receive)(CamelImapStore *store, struct _CamelImapMsg *m),
222 void (*free)(CamelImapStore *store, struct _CamelImapMsg *m),
227 g_assert(size >= sizeof(*msg));
229 msg = g_malloc0(size);
230 msg->receive = receive;
236 void camel_imap_msg_queue(CamelImapStore *store, CamelImapMsg *msg)
238 e_thread_put(store->async_thread, (EMsg *)msg);
244 camel_imap_store_init (gpointer object, gpointer klass)
246 CamelImapStore *imap_store = CAMEL_IMAP_STORE (object);
248 imap_store->istream = NULL;
249 imap_store->ostream = NULL;
251 imap_store->dir_sep = '\0';
252 imap_store->current_folder = NULL;
253 imap_store->connected = FALSE;
255 imap_store->tag_prefix = imap_tag_prefix++;
256 if (imap_tag_prefix > 'Z')
257 imap_tag_prefix = 'A';
259 #ifdef ENABLE_THREADS
260 imap_store->async_thread = e_thread_new(E_THREAD_QUEUE);
261 e_thread_set_msg_destroy(imap_store->async_thread, async_destroy, imap_store);
262 e_thread_set_msg_received(imap_store->async_thread, async_received, imap_store);
263 #endif /* ENABLE_THREADS */
267 camel_imap_store_get_type (void)
269 static CamelType camel_imap_store_type = CAMEL_INVALID_TYPE;
271 if (camel_imap_store_type == CAMEL_INVALID_TYPE) {
272 camel_imap_store_type =
273 camel_type_register (CAMEL_DISCO_STORE_TYPE,
275 sizeof (CamelImapStore),
276 sizeof (CamelImapStoreClass),
277 (CamelObjectClassInitFunc) camel_imap_store_class_init,
279 (CamelObjectInitFunc) camel_imap_store_init,
280 (CamelObjectFinalizeFunc) camel_imap_store_finalize);
283 return camel_imap_store_type;
287 construct (CamelService *service, CamelSession *session,
288 CamelProvider *provider, CamelURL *url,
291 CamelImapStore *imap_store = CAMEL_IMAP_STORE (service);
292 CamelStore *store = CAMEL_STORE (service);
294 CamelURL *summary_url;
296 CAMEL_SERVICE_CLASS (parent_class)->construct (service, session, provider, url, ex);
297 if (camel_exception_is_set (ex))
300 imap_store->storage_path = camel_session_get_storage_path (session, service, ex);
301 if (!imap_store->storage_path)
305 imap_store->base_url = camel_url_to_string (service->url, (CAMEL_URL_HIDE_PASSWORD |
306 CAMEL_URL_HIDE_PARAMS |
307 CAMEL_URL_HIDE_AUTH));
309 imap_store->parameters = 0;
310 if (camel_url_get_param (url, "use_lsub"))
311 store->flags |= CAMEL_STORE_SUBSCRIPTIONS;
312 if (camel_url_get_param (url, "namespace")) {
313 imap_store->parameters |= IMAP_PARAM_OVERRIDE_NAMESPACE;
314 g_free(imap_store->namespace);
315 imap_store->namespace = g_strdup (camel_url_get_param (url, "namespace"));
317 if (camel_url_get_param (url, "check_all"))
318 imap_store->parameters |= IMAP_PARAM_CHECK_ALL;
319 if (camel_url_get_param (url, "filter")) {
320 imap_store->parameters |= IMAP_PARAM_FILTER_INBOX;
321 store->flags |= CAMEL_STORE_FILTER_INBOX;
324 /* setup/load the store summary */
325 tmp = alloca(strlen(imap_store->storage_path)+32);
326 sprintf(tmp, "%s/.ev-store-summary", imap_store->storage_path);
327 imap_store->summary = camel_imap_store_summary_new();
328 camel_store_summary_set_filename((CamelStoreSummary *)imap_store->summary, tmp);
329 summary_url = camel_url_new(imap_store->base_url, NULL);
330 camel_store_summary_set_uri_base((CamelStoreSummary *)imap_store->summary, summary_url);
331 camel_url_free(summary_url);
332 if (camel_store_summary_load((CamelStoreSummary *)imap_store->summary) == 0) {
333 CamelImapStoreSummary *is = imap_store->summary;
336 /* if namespace has changed, clear folder list */
337 if (imap_store->namespace && strcmp(imap_store->namespace, is->namespace->full_name) != 0) {
338 camel_store_summary_clear((CamelStoreSummary *)is);
340 imap_store->namespace = g_strdup(is->namespace->full_name);
341 imap_store->dir_sep = is->namespace->sep;
342 store->dir_sep = is->namespace->sep;
346 imap_store->capabilities = is->capabilities;
347 imap_set_server_level(imap_store);
352 imap_setv (CamelObject *object, CamelException *ex, CamelArgV *args)
354 CamelImapStore *store = (CamelImapStore *) object;
358 for (i = 0; i < args->argc; i++) {
359 tag = args->argv[i].tag;
361 /* make sure this arg wasn't already handled */
362 if (tag & CAMEL_ARG_IGNORE)
365 /* make sure this is an arg we're supposed to handle */
366 if ((tag & CAMEL_ARG_TAG) <= CAMEL_IMAP_STORE_ARG_FIRST ||
367 (tag & CAMEL_ARG_TAG) >= CAMEL_IMAP_STORE_ARG_FIRST + 100)
370 if (tag == CAMEL_IMAP_STORE_NAMESPACE) {
371 if (strcmp (store->namespace, args->argv[i].ca_str) != 0) {
372 g_free (store->namespace);
373 store->namespace = g_strdup (args->argv[i].ca_str);
374 /* the current imap code will need to do a reconnect for this to take effect */
375 /*reconnect = TRUE;*/
377 } else if (tag == CAMEL_IMAP_STORE_OVERRIDE_NAMESPACE) {
378 flags = args->argv[i].ca_int ? IMAP_PARAM_OVERRIDE_NAMESPACE : 0;
379 flags |= (store->parameters & ~IMAP_PARAM_OVERRIDE_NAMESPACE);
381 if (store->parameters != flags) {
382 store->parameters = flags;
383 /* the current imap code will need to do a reconnect for this to take effect */
384 /*reconnect = TRUE;*/
386 } else if (tag == CAMEL_IMAP_STORE_CHECK_ALL) {
387 flags = args->argv[i].ca_int ? IMAP_PARAM_CHECK_ALL : 0;
388 flags |= (store->parameters & ~IMAP_PARAM_CHECK_ALL);
389 store->parameters = flags;
390 /* no need to reconnect for this option to take effect... */
391 } else if (tag == CAMEL_IMAP_STORE_FILTER_INBOX) {
392 flags = args->argv[i].ca_int ? IMAP_PARAM_FILTER_INBOX : 0;
393 flags |= (store->parameters & ~IMAP_PARAM_FILTER_INBOX);
394 store->parameters = flags;
395 /* no need to reconnect for this option to take effect... */
401 /* let our parent know that we've handled this arg */
402 camel_argv_ignore (args, i);
405 /* FIXME: if we need to reconnect for a change to take affect,
406 we need to do it here... or, better yet, somehow chain it
407 up to CamelService's setv implementation. */
409 return CAMEL_OBJECT_CLASS (parent_class)->setv (object, ex, args);
413 imap_getv (CamelObject *object, CamelException *ex, CamelArgGetV *args)
415 CamelImapStore *store = (CamelImapStore *) object;
419 for (i = 0; i < args->argc; i++) {
420 tag = args->argv[i].tag;
422 /* make sure this is an arg we're supposed to handle */
423 if ((tag & CAMEL_ARG_TAG) <= CAMEL_IMAP_STORE_ARG_FIRST ||
424 (tag & CAMEL_ARG_TAG) >= CAMEL_IMAP_STORE_ARG_FIRST + 100)
428 case CAMEL_IMAP_STORE_NAMESPACE:
429 /* get the username */
430 *args->argv[i].ca_str = store->namespace;
432 case CAMEL_IMAP_STORE_OVERRIDE_NAMESPACE:
433 /* get the auth mechanism */
434 *args->argv[i].ca_int = store->parameters & IMAP_PARAM_OVERRIDE_NAMESPACE ? TRUE : FALSE;
436 case CAMEL_IMAP_STORE_CHECK_ALL:
437 /* get the hostname */
438 *args->argv[i].ca_int = store->parameters & IMAP_PARAM_CHECK_ALL ? TRUE : FALSE;
440 case CAMEL_IMAP_STORE_FILTER_INBOX:
442 *args->argv[i].ca_int = store->parameters & IMAP_PARAM_FILTER_INBOX ? TRUE : FALSE;
450 return CAMEL_OBJECT_CLASS (parent_class)->getv (object, ex, args);
454 imap_get_name (CamelService *service, gboolean brief)
457 return g_strdup_printf (_("IMAP server %s"), service->url->host);
459 return g_strdup_printf (_("IMAP service for %s on %s"),
460 service->url->user, service->url->host);
464 imap_set_server_level (CamelImapStore *store)
466 if (store->capabilities & IMAP_CAPABILITY_IMAP4REV1) {
467 store->server_level = IMAP_LEVEL_IMAP4REV1;
468 store->capabilities |= IMAP_CAPABILITY_STATUS;
469 } else if (store->capabilities & IMAP_CAPABILITY_IMAP4)
470 store->server_level = IMAP_LEVEL_IMAP4;
472 store->server_level = IMAP_LEVEL_UNKNOWN;
479 { "IMAP4", IMAP_CAPABILITY_IMAP4 },
480 { "IMAP4REV1", IMAP_CAPABILITY_IMAP4REV1 },
481 { "STATUS", IMAP_CAPABILITY_STATUS },
482 { "NAMESPACE", IMAP_CAPABILITY_NAMESPACE },
483 { "UIDPLUS", IMAP_CAPABILITY_UIDPLUS },
484 { "LITERAL+", IMAP_CAPABILITY_LITERALPLUS },
485 { "STARTTLS", IMAP_CAPABILITY_STARTTLS },
491 imap_get_capability (CamelService *service, CamelException *ex)
493 CamelImapStore *store = CAMEL_IMAP_STORE (service);
494 CamelImapResponse *response;
495 char *result, *capa, *lasts;
498 CAMEL_SERVICE_ASSERT_LOCKED (store, connect_lock);
500 /* Find out the IMAP capabilities */
501 /* We assume we have utf8 capable search until a failed search tells us otherwise */
502 store->capabilities = IMAP_CAPABILITY_utf8_search;
503 store->authtypes = g_hash_table_new (g_str_hash, g_str_equal);
504 response = camel_imap_command (store, NULL, ex, "CAPABILITY");
507 result = camel_imap_response_extract (store, response, "CAPABILITY ", ex);
511 /* Skip over "* CAPABILITY ". */
513 for (capa = strtok_r (capa, " ", &lasts); capa;
514 capa = strtok_r (NULL, " ", &lasts)) {
515 if (!strncmp (capa, "AUTH=", 5)) {
516 g_hash_table_insert (store->authtypes,
518 GINT_TO_POINTER (1));
521 for (i = 0; capabilities[i].name; i++) {
522 if (strcasecmp (capa, capabilities[i].name) == 0) {
523 store->capabilities |= capabilities[i].flag;
530 imap_set_server_level (store);
532 if (store->summary->capabilities != store->capabilities) {
533 store->summary->capabilities = store->capabilities;
534 camel_store_summary_touch((CamelStoreSummary *)store->summary);
535 camel_store_summary_save((CamelStoreSummary *)store->summary);
544 USE_SSL_WHEN_POSSIBLE
547 #define SSL_PORT_FLAGS (CAMEL_TCP_STREAM_SSL_ENABLE_SSL2 | CAMEL_TCP_STREAM_SSL_ENABLE_SSL3)
548 #define STARTTLS_FLAGS (CAMEL_TCP_STREAM_SSL_ENABLE_TLS)
551 connect_to_server (CamelService *service, int ssl_mode, int try_starttls, CamelException *ex)
553 CamelImapStore *store = (CamelImapStore *) service;
554 CamelImapResponse *response;
555 CamelStream *tcp_stream;
561 h = camel_service_gethost (service, ex);
565 port = service->url->port ? service->url->port : 143;
568 if (ssl_mode != USE_SSL_NEVER) {
570 tcp_stream = camel_tcp_stream_ssl_new_raw (service, service->url->host, STARTTLS_FLAGS);
572 port = service->url->port ? service->url->port : 993;
573 tcp_stream = camel_tcp_stream_ssl_new (service, service->url->host, SSL_PORT_FLAGS);
576 tcp_stream = camel_tcp_stream_raw_new ();
579 tcp_stream = camel_tcp_stream_raw_new ();
580 #endif /* HAVE_SSL */
582 printf ("trying to connect to %s:%d...", service->url->host, port);
584 ret = camel_tcp_stream_connect (CAMEL_TCP_STREAM (tcp_stream), h, port);
588 camel_exception_set (ex, CAMEL_EXCEPTION_USER_CANCEL,
589 _("Connection cancelled"));
591 camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
592 _("Could not connect to %s (port %d): %s"),
593 service->url->host, port, g_strerror (errno));
595 printf ("failed: %s\n", g_strerror (errno));
597 camel_object_unref (CAMEL_OBJECT (tcp_stream));
602 printf ("connected...");
604 store->ostream = tcp_stream;
605 store->istream = camel_stream_buffer_new (tcp_stream, CAMEL_STREAM_BUFFER_READ);
607 store->connected = TRUE;
610 /* Read the greeting, if any. FIXME: deal with PREAUTH */
611 if (camel_imap_store_readline (store, &buf, ex) < 0) {
612 printf ("failed when trying to read greeting: %s\n", g_strerror (errno));
614 if (store->istream) {
615 camel_object_unref (CAMEL_OBJECT (store->istream));
616 store->istream = NULL;
619 if (store->ostream) {
620 camel_object_unref (CAMEL_OBJECT (store->ostream));
621 store->ostream = NULL;
624 store->connected = FALSE;
630 /* get the imap server capabilities */
631 if (!imap_get_capability (service, ex)) {
632 printf ("failed to get capabilities: %s\n", g_strerror (errno));
634 if (store->istream) {
635 camel_object_unref (CAMEL_OBJECT (store->istream));
636 store->istream = NULL;
639 if (store->ostream) {
640 camel_object_unref (CAMEL_OBJECT (store->ostream));
641 store->ostream = NULL;
644 store->connected = FALSE;
649 if (ssl_mode == USE_SSL_WHEN_POSSIBLE) {
650 if (store->capabilities & IMAP_CAPABILITY_STARTTLS)
652 } else if (ssl_mode == USE_SSL_ALWAYS) {
654 if (store->capabilities & IMAP_CAPABILITY_STARTTLS) {
655 /* attempt to toggle STARTTLS mode */
658 /* server doesn't support STARTTLS, abort */
659 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
660 _("Failed to connect to IMAP server %s in secure mode: %s"),
661 service->url->host, _("SSL/TLS extension not supported."));
662 /* we have the possibility of quitting cleanly here */
668 #endif /* HAVE_SSL */
670 printf ("success\n");
677 /* as soon as we send a STARTTLS command, all hope is lost of a clean QUIT if problems arise */
680 response = camel_imap_command (store, NULL, ex, "STARTTLS");
682 printf ("STARTTLS command failed: %s\n", g_strerror (errno));
683 camel_object_unref (CAMEL_OBJECT (store->istream));
684 camel_object_unref (CAMEL_OBJECT (store->ostream));
685 store->istream = store->ostream = NULL;
689 camel_imap_response_free_without_processing (store, response);
691 /* Okay, now toggle SSL/TLS mode */
692 if (camel_tcp_stream_ssl_enable_ssl (CAMEL_TCP_STREAM_SSL (tcp_stream)) == -1) {
693 printf ("failed toggling into STARTTLS mode: %s\n", g_strerror (errno));
694 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
695 _("Failed to connect to IMAP server %s in secure mode: %s"),
696 service->url->host, _("SSL negotiations failed"));
700 /* rfc2595, section 4 states that after a successful STLS
701 command, the client MUST discard prior CAPA responses */
702 if (!imap_get_capability (service, ex)) {
703 printf ("failed getting capabilities after STARTLS: %s\n", g_strerror (errno));
705 if (store->istream) {
706 camel_object_unref (CAMEL_OBJECT (store->istream));
707 store->istream = NULL;
710 if (store->ostream) {
711 camel_object_unref (CAMEL_OBJECT (store->ostream));
712 store->ostream = NULL;
715 store->connected = FALSE;
720 printf ("success\n");
726 if (clean_quit && store->connected) {
727 /* try to disconnect cleanly */
728 response = camel_imap_command (store, NULL, ex, "LOGOUT");
730 camel_imap_response_free_without_processing (store, response);
733 if (store->istream) {
734 camel_object_unref (CAMEL_OBJECT (store->istream));
735 store->istream = NULL;
738 if (store->ostream) {
739 camel_object_unref (CAMEL_OBJECT (store->ostream));
740 store->ostream = NULL;
743 store->connected = FALSE;
746 #endif /* HAVE_SSL */
753 { "", USE_SSL_ALWAYS },
754 { "always", USE_SSL_ALWAYS },
755 { "when-possible", USE_SSL_WHEN_POSSIBLE },
756 { "never", USE_SSL_NEVER },
757 { NULL, USE_SSL_NEVER },
761 connect_to_server_wrapper (CamelService *service, CamelException *ex)
767 use_ssl = camel_url_get_param (service->url, "use_ssl");
769 for (i = 0; ssl_options[i].value; i++)
770 if (!strcmp (ssl_options[i].value, use_ssl))
772 ssl_mode = ssl_options[i].mode;
774 ssl_mode = USE_SSL_NEVER;
776 if (ssl_mode == USE_SSL_ALWAYS) {
777 /* First try the ssl port */
778 if (!connect_to_server (service, ssl_mode, FALSE, ex)) {
779 if (camel_exception_get_id (ex) == CAMEL_EXCEPTION_SERVICE_UNAVAILABLE) {
780 /* The ssl port seems to be unavailable, lets try STARTTLS */
781 camel_exception_clear (ex);
782 return connect_to_server (service, ssl_mode, TRUE, ex);
789 } else if (ssl_mode == USE_SSL_WHEN_POSSIBLE) {
790 /* If the server supports STARTTLS, use it */
791 return connect_to_server (service, ssl_mode, TRUE, ex);
793 /* User doesn't care about SSL */
794 return connect_to_server (service, ssl_mode, FALSE, ex);
797 return connect_to_server (service, USE_SSL_NEVER, FALSE, ex);
801 extern CamelServiceAuthType camel_imap_password_authtype;
804 query_auth_types (CamelService *service, CamelException *ex)
806 CamelImapStore *store = CAMEL_IMAP_STORE (service);
807 CamelServiceAuthType *authtype;
808 GList *sasl_types, *t, *next;
811 if (!camel_disco_store_check_online (CAMEL_DISCO_STORE (store), ex))
814 CAMEL_SERVICE_LOCK (store, connect_lock);
815 connected = connect_to_server_wrapper (service, ex);
816 CAMEL_SERVICE_UNLOCK (store, connect_lock);
820 sasl_types = camel_sasl_authtype_list (FALSE);
821 for (t = sasl_types; t; t = next) {
825 if (!g_hash_table_lookup (store->authtypes, authtype->authproto)) {
826 sasl_types = g_list_remove_link (sasl_types, t);
831 return g_list_prepend (sasl_types, &camel_imap_password_authtype);
834 /* folder_name is path name */
835 static CamelFolderInfo *
836 imap_build_folder_info(CamelImapStore *imap_store, const char *folder_name)
842 fi = g_malloc0(sizeof(*fi));
844 fi->full_name = g_strdup(folder_name);
845 fi->unread_message_count = 0;
847 url = camel_url_new (imap_store->base_url, NULL);
849 url->path = g_strdup_printf ("/%s", folder_name);
850 fi->url = camel_url_to_string (url, CAMEL_URL_HIDE_ALL);
852 fi->path = g_strdup_printf("/%s", folder_name);
853 name = strrchr (fi->path, '/');
859 fi->name = g_strdup (name);
865 imap_folder_effectively_unsubscribed(CamelImapStore *imap_store,
866 const char *folder_name, CamelException *ex)
871 si = camel_store_summary_path((CamelStoreSummary *)imap_store->summary, folder_name);
873 if (si->flags & CAMEL_STORE_INFO_FOLDER_SUBSCRIBED) {
874 si->flags &= ~CAMEL_STORE_INFO_FOLDER_SUBSCRIBED;
875 camel_store_summary_touch((CamelStoreSummary *)imap_store->summary);
876 camel_store_summary_save((CamelStoreSummary *)imap_store->summary);
878 camel_store_summary_info_free((CamelStoreSummary *)imap_store->summary, si);
881 if (imap_store->renaming) {
882 /* we don't need to emit a "folder_unsubscribed" signal
883 if we are in the process of renaming folders, so we
889 fi = imap_build_folder_info(imap_store, folder_name);
890 camel_object_trigger_event (CAMEL_OBJECT (imap_store), "folder_unsubscribed", fi);
891 camel_folder_info_free (fi);
895 imap_forget_folder (CamelImapStore *imap_store, const char *folder_name, CamelException *ex)
897 CamelFolderSummary *summary;
898 CamelImapMessageCache *cache;
901 char *folder_dir, *storage_path;
905 name = strrchr (folder_name, imap_store->dir_sep);
911 storage_path = g_strdup_printf ("%s/folders", imap_store->storage_path);
912 folder_dir = e_path_to_physical (storage_path, folder_name);
913 g_free (storage_path);
914 if (access (folder_dir, F_OK) != 0) {
919 summary_file = g_strdup_printf ("%s/summary", folder_dir);
920 summary = camel_imap_summary_new (summary_file);
922 g_free (summary_file);
927 cache = camel_imap_message_cache_new (folder_dir, summary, ex);
929 camel_imap_message_cache_clear (cache);
931 camel_object_unref (cache);
932 camel_object_unref (summary);
934 unlink (summary_file);
935 g_free (summary_file);
937 journal_file = g_strdup_printf ("%s/summary", folder_dir);
938 unlink (journal_file);
939 g_free (journal_file);
946 camel_store_summary_remove_path((CamelStoreSummary *)imap_store->summary, folder_name);
947 camel_store_summary_save((CamelStoreSummary *)imap_store->summary);
949 fi = imap_build_folder_info(imap_store, folder_name);
950 camel_object_trigger_event (CAMEL_OBJECT (imap_store), "folder_deleted", fi);
951 camel_folder_info_free (fi);
955 imap_check_folder_still_extant (CamelImapStore *imap_store, const char *full_name,
958 CamelImapResponse *response;
960 response = camel_imap_command (imap_store, NULL, ex, "LIST \"\" %S",
964 gboolean stillthere = response->untagged->len != 0;
966 camel_imap_response_free_without_processing (imap_store, response);
971 /* if the command was rejected, there must be some other error,
972 assume it worked so we dont blow away the folder unecessarily */
976 /* This is a little 'hack' to avoid the deadlock conditions that would otherwise
977 ensue when calling camel_folder_refresh_info from inside a lock */
978 /* NB: on second thougts this is probably not entirely safe, but it'll do for now */
979 /* No, its definetly not safe. So its been changed to copy the folders first */
980 /* the alternative is to:
981 make the camel folder->lock recursive (which should probably be done)
982 or remove it from camel_folder_refresh_info, and use another locking mechanism */
983 /* also see get_folder_info_online() for the same hack repeated */
985 imap_store_refresh_folders (CamelImapStore *store, CamelException *ex)
990 folders = camel_object_bag_list(CAMEL_STORE (store)->folders);
992 for (i = 0; i <folders->len; i++) {
993 CamelFolder *folder = folders->pdata[i];
995 CAMEL_IMAP_FOLDER (folder)->need_rescan = TRUE;
996 if (!camel_exception_is_set(ex))
997 CAMEL_FOLDER_CLASS (CAMEL_OBJECT_GET_CLASS(folder))->refresh_info(folder, ex);
999 if (camel_exception_is_set (ex) &&
1000 imap_check_folder_still_extant (store, folder->full_name, ex) == FALSE) {
1003 /* the folder was deleted (may happen when we come back online
1004 * after being offline */
1006 namedup = g_strdup (folder->full_name);
1007 camel_object_unref(folder);
1008 imap_folder_effectively_unsubscribed (store, namedup, ex);
1009 imap_forget_folder (store, namedup, ex);
1012 camel_object_unref(folder);
1015 g_ptr_array_free (folders, TRUE);
1019 try_auth (CamelImapStore *store, const char *mech, CamelException *ex)
1022 CamelImapResponse *response;
1026 CAMEL_SERVICE_ASSERT_LOCKED (store, connect_lock);
1028 response = camel_imap_command (store, NULL, ex, "AUTHENTICATE %s", mech);
1032 sasl = camel_sasl_new ("imap", mech, CAMEL_SERVICE (store));
1033 while (!camel_sasl_authenticated (sasl)) {
1034 resp = camel_imap_response_extract_continuation (store, response, ex);
1038 sasl_resp = camel_sasl_challenge_base64 (sasl, imap_next_word (resp), ex);
1040 if (camel_exception_is_set (ex))
1041 goto break_and_lose;
1043 response = camel_imap_command_continuation (store, sasl_resp, strlen (sasl_resp), ex);
1049 resp = camel_imap_response_extract_continuation (store, response, NULL);
1051 /* Oops. SASL claims we're done, but the IMAP server
1052 * doesn't think so...
1058 camel_object_unref (CAMEL_OBJECT (sasl));
1063 /* Get the server out of "waiting for continuation data" mode. */
1064 response = camel_imap_command_continuation (store, "*", 1, NULL);
1066 camel_imap_response_free (store, response);
1069 if (!camel_exception_is_set (ex)) {
1070 camel_exception_set (ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE,
1071 _("Bad authentication response from server."));
1074 camel_object_unref (CAMEL_OBJECT (sasl));
1080 imap_auth_loop (CamelService *service, CamelException *ex)
1082 CamelImapStore *store = CAMEL_IMAP_STORE (service);
1083 CamelSession *session = camel_service_get_session (service);
1084 CamelServiceAuthType *authtype = NULL;
1085 CamelImapResponse *response;
1086 char *errbuf = NULL;
1087 gboolean authenticated = FALSE;
1089 CAMEL_SERVICE_ASSERT_LOCKED (store, connect_lock);
1091 if (service->url->authmech) {
1092 if (!g_hash_table_lookup (store->authtypes, service->url->authmech)) {
1093 camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE,
1094 _("IMAP server %s does not support requested "
1095 "authentication type %s"),
1097 service->url->authmech);
1101 authtype = camel_sasl_authtype (service->url->authmech);
1103 camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE,
1104 _("No support for authentication type %s"),
1105 service->url->authmech);
1109 if (!authtype->need_password) {
1110 authenticated = try_auth (store, authtype->authproto, ex);
1116 while (!authenticated) {
1118 /* We need to un-cache the password before prompting again */
1119 camel_session_forget_password (session, service, "password", ex);
1120 g_free (service->url->passwd);
1121 service->url->passwd = NULL;
1124 if (!service->url->passwd) {
1127 prompt = g_strdup_printf (_("%sPlease enter the IMAP "
1128 "password for %s@%s"),
1129 errbuf ? errbuf : "",
1131 service->url->host);
1132 service->url->passwd =
1133 camel_session_get_password (session, prompt, FALSE, TRUE,
1134 service, "password", ex);
1139 if (!service->url->passwd) {
1140 camel_exception_set (ex, CAMEL_EXCEPTION_USER_CANCEL,
1141 _("You didn't enter a password."));
1146 if (!store->connected) {
1147 /* Some servers (eg, courier) will disconnect on
1148 * a bad password. So reconnect here.
1150 if (!connect_to_server_wrapper (service, ex))
1155 authenticated = try_auth (store, authtype->authproto, ex);
1157 response = camel_imap_command (store, NULL, ex,
1160 service->url->passwd);
1162 camel_imap_response_free (store, response);
1163 authenticated = TRUE;
1166 if (!authenticated) {
1167 if (camel_exception_get_id(ex) == CAMEL_EXCEPTION_USER_CANCEL)
1170 errbuf = g_strdup_printf (_("Unable to authenticate "
1171 "to IMAP server.\n%s\n\n"),
1172 camel_exception_get_description (ex));
1173 camel_exception_clear (ex);
1181 can_work_offline (CamelDiscoStore *disco_store)
1183 CamelImapStore *store = CAMEL_IMAP_STORE (disco_store);
1185 return camel_store_summary_count((CamelStoreSummary *)store->summary) != 0;
1189 imap_connect_online (CamelService *service, CamelException *ex)
1191 CamelImapStore *store = CAMEL_IMAP_STORE (service);
1192 CamelDiscoStore *disco_store = CAMEL_DISCO_STORE (service);
1193 CamelImapResponse *response;
1194 /*struct _namespaces *namespaces;*/
1195 char *result, *name, *path;
1198 CamelImapStoreNamespace *ns;
1200 CAMEL_SERVICE_LOCK (store, connect_lock);
1201 if (!connect_to_server_wrapper (service, ex) ||
1202 !imap_auth_loop (service, ex)) {
1203 CAMEL_SERVICE_UNLOCK (store, connect_lock);
1204 camel_service_disconnect (service, TRUE, NULL);
1208 /* Get namespace and hierarchy separator */
1209 if ((store->capabilities & IMAP_CAPABILITY_NAMESPACE) &&
1210 !(store->parameters & IMAP_PARAM_OVERRIDE_NAMESPACE)) {
1211 response = camel_imap_command (store, NULL, ex, "NAMESPACE");
1215 result = camel_imap_response_extract (store, response, "NAMESPACE", ex);
1221 namespaces = imap_parse_namespace_response (result);
1222 imap_namespaces_destroy (namespaces);
1226 name = camel_strstrcase (result, "NAMESPACE ((");
1231 store->namespace = imap_parse_string ((const char **) &name, &len);
1232 if (name && *name++ == ' ') {
1233 sep = imap_parse_string ((const char **) &name, &len);
1235 store->dir_sep = *sep;
1236 ((CamelStore *)store)->dir_sep = store->dir_sep;
1244 if (!store->namespace)
1245 store->namespace = g_strdup ("");
1247 if (!store->dir_sep) {
1248 if (store->server_level >= IMAP_LEVEL_IMAP4REV1) {
1249 /* This idiom means "tell me the hierarchy separator
1250 * for the given path, even if that path doesn't exist.
1252 response = camel_imap_command (store, NULL, ex,
1256 /* Plain IMAP4 doesn't have that idiom, so we fall back
1257 * to "tell me about this folder", which will fail if
1258 * the folder doesn't exist (eg, if namespace is "").
1260 response = camel_imap_command (store, NULL, ex,
1267 result = camel_imap_response_extract (store, response, "LIST", NULL);
1269 imap_parse_list_response (store, result, NULL, &store->dir_sep, NULL);
1272 if (!store->dir_sep) {
1273 store->dir_sep = '/'; /* Guess */
1274 ((CamelStore *)store)->dir_sep = store->dir_sep;
1278 /* canonicalize the namespace to end with dir_sep */
1279 len = strlen (store->namespace);
1280 if (len && store->namespace[len - 1] != store->dir_sep) {
1283 tmp = g_strdup_printf ("%s%c", store->namespace, store->dir_sep);
1284 g_free (store->namespace);
1285 store->namespace = tmp;
1288 ns = camel_imap_store_summary_namespace_new(store->summary, store->namespace, store->dir_sep);
1289 camel_imap_store_summary_namespace_set(store->summary, ns);
1291 if (CAMEL_STORE (store)->flags & CAMEL_STORE_SUBSCRIPTIONS) {
1292 gboolean haveinbox = FALSE;
1296 /* this pre-fills the summary, and checks that lsub is useful */
1297 folders = g_ptr_array_new ();
1298 pattern = g_strdup_printf ("%s*", store->namespace);
1299 get_folders_online (store, pattern, folders, TRUE, ex);
1302 for (i = 0; i < folders->len; i++) {
1303 CamelFolderInfo *fi = folders->pdata[i];
1305 haveinbox = haveinbox || !strcasecmp (fi->full_name, "INBOX");
1307 if (fi->flags & (CAMEL_IMAP_FOLDER_MARKED | CAMEL_IMAP_FOLDER_UNMARKED))
1308 store->capabilities |= IMAP_CAPABILITY_useful_lsub;
1309 camel_folder_info_free (fi);
1312 /* if the namespace is under INBOX, check INBOX explicitly */
1313 if (!strncasecmp (store->namespace, "INBOX", 5) && !camel_exception_is_set (ex)) {
1314 gboolean just_subscribed = FALSE;
1315 gboolean need_subscribe = FALSE;
1318 g_ptr_array_set_size (folders, 0);
1319 get_folders_online (store, "INBOX", folders, TRUE, ex);
1321 for (i = 0; i < folders->len; i++) {
1322 CamelFolderInfo *fi = folders->pdata[i];
1324 /* this should always be TRUE if folders->len > 0 */
1325 if (!strcasecmp (fi->full_name, "INBOX")) {
1328 /* if INBOX is marked as \NoSelect then it is probably
1329 because it has not been subscribed to */
1330 if (!need_subscribe)
1331 need_subscribe = fi->flags & CAMEL_FOLDER_NOSELECT;
1334 camel_folder_info_free (fi);
1337 need_subscribe = !haveinbox || need_subscribe;
1338 if (need_subscribe && !just_subscribed && !camel_exception_is_set (ex)) {
1339 /* in order to avoid user complaints, force a subscription to INBOX */
1340 response = camel_imap_command (store, NULL, ex, "SUBSCRIBE INBOX");
1341 if (response != NULL) {
1342 /* force a re-check which will pre-fill the summary and
1343 also get any folder flags present on the INBOX */
1344 camel_imap_response_free (store, response);
1345 just_subscribed = TRUE;
1351 g_ptr_array_free (folders, TRUE);
1354 path = g_strdup_printf ("%s/journal", store->storage_path);
1355 disco_store->diary = camel_disco_diary_new (disco_store, path, ex);
1359 /* save any changes we had */
1360 camel_store_summary_save((CamelStoreSummary *)store->summary);
1362 CAMEL_SERVICE_UNLOCK (store, connect_lock);
1364 if (camel_exception_is_set (ex))
1365 camel_service_disconnect (service, TRUE, NULL);
1366 else if (camel_disco_diary_empty (disco_store->diary))
1367 imap_store_refresh_folders (store, ex);
1369 return !camel_exception_is_set (ex);
1373 imap_connect_offline (CamelService *service, CamelException *ex)
1375 CamelImapStore *store = CAMEL_IMAP_STORE (service);
1376 CamelDiscoStore *disco_store = CAMEL_DISCO_STORE (service);
1379 path = g_strdup_printf ("%s/journal", store->storage_path);
1380 disco_store->diary = camel_disco_diary_new (disco_store, path, ex);
1382 if (!disco_store->diary)
1385 imap_store_refresh_folders (store, ex);
1387 store->connected = !camel_exception_is_set (ex);
1388 return store->connected;
1392 imap_disconnect_offline (CamelService *service, gboolean clean, CamelException *ex)
1394 CamelImapStore *store = CAMEL_IMAP_STORE (service);
1395 CamelDiscoStore *disco = CAMEL_DISCO_STORE (service);
1397 store->connected = FALSE;
1398 if (store->current_folder) {
1399 camel_object_unref (CAMEL_OBJECT (store->current_folder));
1400 store->current_folder = NULL;
1403 if (store->authtypes) {
1404 g_hash_table_foreach_remove (store->authtypes,
1406 g_hash_table_destroy (store->authtypes);
1407 store->authtypes = NULL;
1410 if (store->namespace && !(store->parameters & IMAP_PARAM_OVERRIDE_NAMESPACE)) {
1411 g_free (store->namespace);
1412 store->namespace = NULL;
1416 camel_object_unref (CAMEL_OBJECT (disco->diary));
1417 disco->diary = NULL;
1424 imap_disconnect_online (CamelService *service, gboolean clean, CamelException *ex)
1426 CamelImapStore *store = CAMEL_IMAP_STORE (service);
1427 CamelImapResponse *response;
1429 if (store->connected && clean) {
1430 response = camel_imap_command (store, NULL, NULL, "LOGOUT");
1431 camel_imap_response_free (store, response);
1434 if (store->istream) {
1435 camel_object_unref (CAMEL_OBJECT (store->istream));
1436 store->istream = NULL;
1439 if (store->ostream) {
1440 camel_object_unref (CAMEL_OBJECT (store->ostream));
1441 store->ostream = NULL;
1444 imap_disconnect_offline (service, clean, ex);
1451 imap_summary_is_dirty (CamelFolderSummary *summary)
1453 CamelMessageInfo *info;
1456 max = camel_folder_summary_count (summary);
1457 for (i = 0; i < max; i++) {
1458 info = camel_folder_summary_index (summary, i);
1459 if (info && (info->flags & CAMEL_MESSAGE_FOLDER_FLAGGED))
1467 imap_noop (CamelStore *store, CamelException *ex)
1469 CamelImapStore *imap_store = (CamelImapStore *) store;
1470 CamelDiscoStore *disco = (CamelDiscoStore *) store;
1471 CamelImapResponse *response;
1472 CamelFolder *current_folder;
1474 if (camel_disco_store_status (disco) != CAMEL_DISCO_STORE_ONLINE)
1477 CAMEL_SERVICE_LOCK (imap_store, connect_lock);
1479 current_folder = imap_store->current_folder;
1480 if (current_folder && imap_summary_is_dirty (current_folder->summary)) {
1481 /* let's sync the flags instead */
1482 camel_folder_sync (current_folder, FALSE, ex);
1484 response = camel_imap_command (imap_store, NULL, ex, "NOOP");
1486 camel_imap_response_free (imap_store, response);
1489 CAMEL_SERVICE_UNLOCK (imap_store, connect_lock);
1493 hash_folder_name (gconstpointer key)
1495 if (strcasecmp (key, "INBOX") == 0)
1496 return g_str_hash ("INBOX");
1498 return g_str_hash (key);
1502 compare_folder_name (gconstpointer a, gconstpointer b)
1504 gconstpointer aname = a, bname = b;
1506 if (strcasecmp (a, "INBOX") == 0)
1508 if (strcasecmp (b, "INBOX") == 0)
1510 return g_str_equal (aname, bname);
1513 static CamelFolder *
1514 no_such_folder (const char *name, CamelException *ex)
1516 camel_exception_setv (ex, CAMEL_EXCEPTION_STORE_NO_FOLDER,
1517 _("No such folder %s"), name);
1522 get_folder_status (CamelImapStore *imap_store, const char *folder_name, const char *type)
1524 CamelImapResponse *response;
1528 /* FIXME: we assume the server is STATUS-capable */
1530 response = camel_imap_command (imap_store, NULL, NULL,
1538 camel_exception_init (&ex);
1539 if (imap_check_folder_still_extant (imap_store, folder_name, &ex) == FALSE) {
1540 imap_folder_effectively_unsubscribed (imap_store, folder_name, &ex);
1541 imap_forget_folder (imap_store, folder_name, &ex);
1543 camel_exception_clear (&ex);
1547 status = camel_imap_response_extract (imap_store, response,
1552 p = camel_strstrcase (status, type);
1554 out = strtoul (p + strlen (type), NULL, 10);
1562 static CamelFolder *
1563 get_folder_online (CamelStore *store, const char *folder_name,
1564 guint32 flags, CamelException *ex)
1566 CamelImapStore *imap_store = CAMEL_IMAP_STORE (store);
1567 CamelImapResponse *response;
1568 CamelFolder *new_folder;
1569 char *folder_dir, *storage_path;
1571 if (!camel_imap_store_connected (imap_store, ex))
1574 if (!strcasecmp (folder_name, "INBOX"))
1575 folder_name = "INBOX";
1577 /* Lock around the whole lot to check/create atomically */
1578 CAMEL_SERVICE_LOCK (imap_store, connect_lock);
1579 if (imap_store->current_folder) {
1580 camel_object_unref (CAMEL_OBJECT (imap_store->current_folder));
1581 imap_store->current_folder = NULL;
1583 response = camel_imap_command (imap_store, NULL, NULL, "SELECT %F", folder_name);
1587 if (!flags & CAMEL_STORE_FOLDER_CREATE) {
1588 CAMEL_SERVICE_UNLOCK (imap_store, connect_lock);
1589 return no_such_folder (folder_name, ex);
1592 folder_real = camel_imap_store_summary_path_to_full(imap_store->summary, folder_name, store->dir_sep);
1594 response = camel_imap_command (imap_store, NULL, ex, "CREATE %S", folder_real);
1597 camel_imap_store_summary_add_from_full(imap_store->summary, folder_real, store->dir_sep);
1599 camel_imap_response_free (imap_store, response);
1601 response = camel_imap_command (imap_store, NULL, NULL, "SELECT %F", folder_name);
1603 g_free(folder_real);
1605 CAMEL_SERVICE_UNLOCK (imap_store, connect_lock);
1610 storage_path = g_strdup_printf("%s/folders", imap_store->storage_path);
1611 folder_dir = e_path_to_physical (storage_path, folder_name);
1612 g_free(storage_path);
1613 new_folder = camel_imap_folder_new (store, folder_name, folder_dir, ex);
1614 g_free (folder_dir);
1616 CamelException local_ex;
1618 imap_store->current_folder = new_folder;
1619 camel_object_ref (CAMEL_OBJECT (new_folder));
1620 camel_exception_init (&local_ex);
1621 camel_imap_folder_selected (new_folder, response, &local_ex);
1623 if (camel_exception_is_set (&local_ex)) {
1624 camel_exception_xfer (ex, &local_ex);
1625 camel_object_unref (CAMEL_OBJECT (imap_store->current_folder));
1626 imap_store->current_folder = NULL;
1627 camel_object_unref (CAMEL_OBJECT (new_folder));
1631 camel_imap_response_free_without_processing (imap_store, response);
1633 CAMEL_SERVICE_UNLOCK (imap_store, connect_lock);
1638 static CamelFolder *
1639 get_folder_offline (CamelStore *store, const char *folder_name,
1640 guint32 flags, CamelException *ex)
1642 CamelImapStore *imap_store = CAMEL_IMAP_STORE (store);
1643 CamelFolder *new_folder;
1644 char *folder_dir, *storage_path;
1646 if (!imap_store->connected &&
1647 !camel_service_connect (CAMEL_SERVICE (store), ex))
1650 if (!strcasecmp (folder_name, "INBOX"))
1651 folder_name = "INBOX";
1653 storage_path = g_strdup_printf("%s/folders", imap_store->storage_path);
1654 folder_dir = e_path_to_physical (storage_path, folder_name);
1655 g_free(storage_path);
1656 if (!folder_dir || access (folder_dir, F_OK) != 0) {
1657 g_free (folder_dir);
1658 camel_exception_setv (ex, CAMEL_EXCEPTION_STORE_NO_FOLDER,
1659 _("No such folder %s"), folder_name);
1663 new_folder = camel_imap_folder_new (store, folder_name, folder_dir, ex);
1664 g_free (folder_dir);
1670 delete_folder (CamelStore *store, const char *folder_name, CamelException *ex)
1672 CamelImapStore *imap_store = CAMEL_IMAP_STORE (store);
1673 CamelImapResponse *response;
1675 if (!camel_disco_store_check_online (CAMEL_DISCO_STORE (store), ex))
1678 /* make sure this folder isn't currently SELECTed */
1679 response = camel_imap_command (imap_store, NULL, ex, "SELECT INBOX");
1681 camel_imap_response_free_without_processing (imap_store, response);
1683 CAMEL_SERVICE_LOCK (imap_store, connect_lock);
1685 if (imap_store->current_folder)
1686 camel_object_unref (CAMEL_OBJECT (imap_store->current_folder));
1687 /* no need to actually create a CamelFolder for INBOX */
1688 imap_store->current_folder = NULL;
1690 CAMEL_SERVICE_UNLOCK (imap_store, connect_lock);
1694 response = camel_imap_command (imap_store, NULL, ex, "DELETE %F",
1698 camel_imap_response_free (imap_store, response);
1699 imap_forget_folder (imap_store, folder_name, ex);
1704 manage_subscriptions (CamelStore *store, const char *old_name, gboolean subscribe)
1706 CamelImapStore *imap_store = CAMEL_IMAP_STORE (store);
1708 int olen = strlen(old_name);
1712 count = camel_store_summary_count((CamelStoreSummary *)imap_store->summary);
1713 for (i=0;i<count;i++) {
1714 si = camel_store_summary_index((CamelStoreSummary *)imap_store->summary, i);
1716 path = camel_store_info_path(imap_store->summary, si);
1717 if (strncmp(path, old_name, olen) == 0) {
1719 subscribe_folder(store, path, NULL);
1721 unsubscribe_folder(store, path, NULL);
1723 camel_store_summary_info_free((CamelStoreSummary *)imap_store->summary, si);
1729 rename_folder_info (CamelImapStore *imap_store, const char *old_name, const char *new_name)
1733 int olen = strlen(old_name);
1735 char *npath, *nfull;
1737 count = camel_store_summary_count((CamelStoreSummary *)imap_store->summary);
1738 for (i=0;i<count;i++) {
1739 si = camel_store_summary_index((CamelStoreSummary *)imap_store->summary, i);
1742 path = camel_store_info_path(imap_store->summary, si);
1743 if (strncmp(path, old_name, olen) == 0) {
1744 if (strlen(path) > olen)
1745 npath = g_strdup_printf("%s/%s", new_name, path+olen+1);
1747 npath = g_strdup(new_name);
1748 nfull = camel_imap_store_summary_path_to_full(imap_store->summary, npath, imap_store->dir_sep);
1750 /* workaround for broken server (courier uses '.') that doesn't rename
1751 subordinate folders as required by rfc 2060 */
1752 if (imap_store->dir_sep == '.') {
1753 CamelImapResponse *response;
1755 response = camel_imap_command (imap_store, NULL, NULL, "RENAME %F %S", path, nfull);
1757 camel_imap_response_free (imap_store, response);
1760 camel_store_info_set_string((CamelStoreSummary *)imap_store->summary, si, CAMEL_STORE_INFO_PATH, npath);
1761 camel_store_info_set_string((CamelStoreSummary *)imap_store->summary, si, CAMEL_IMAP_STORE_INFO_FULL_NAME, nfull);
1763 camel_store_summary_touch((CamelStoreSummary *)imap_store->summary);
1767 camel_store_summary_info_free((CamelStoreSummary *)imap_store->summary, si);
1772 rename_folder (CamelStore *store, const char *old_name, const char *new_name_in, CamelException *ex)
1774 CamelImapStore *imap_store = CAMEL_IMAP_STORE (store);
1775 CamelImapResponse *response;
1776 char *oldpath, *newpath, *storage_path, *new_name;
1778 if (!camel_disco_store_check_online (CAMEL_DISCO_STORE (store), ex))
1781 /* make sure this folder isn't currently SELECTed - it's
1782 actually possible to rename INBOX but if you do another
1783 INBOX will immediately be created by the server */
1784 response = camel_imap_command (imap_store, NULL, ex, "SELECT INBOX");
1786 camel_imap_response_free_without_processing (imap_store, response);
1788 CAMEL_SERVICE_LOCK (imap_store, connect_lock);
1790 if (imap_store->current_folder)
1791 camel_object_unref (CAMEL_OBJECT (imap_store->current_folder));
1792 /* no need to actually create a CamelFolder for INBOX */
1793 imap_store->current_folder = NULL;
1795 CAMEL_SERVICE_UNLOCK (imap_store, connect_lock);
1799 imap_store->renaming = TRUE;
1801 if (store->flags & CAMEL_STORE_SUBSCRIPTIONS)
1802 manage_subscriptions(store, old_name, FALSE);
1804 new_name = camel_imap_store_summary_path_to_full(imap_store->summary, new_name_in, store->dir_sep);
1805 response = camel_imap_command (imap_store, NULL, ex, "RENAME %F %S", old_name, new_name);
1808 if (store->flags & CAMEL_STORE_SUBSCRIPTIONS)
1809 manage_subscriptions(store, old_name, TRUE);
1811 imap_store->renaming = FALSE;
1815 camel_imap_response_free (imap_store, response);
1817 /* rename summary, and handle broken server */
1818 rename_folder_info(imap_store, old_name, new_name_in);
1820 if (store->flags & CAMEL_STORE_SUBSCRIPTIONS)
1821 manage_subscriptions(store, new_name_in, TRUE);
1823 storage_path = g_strdup_printf("%s/folders", imap_store->storage_path);
1824 oldpath = e_path_to_physical (storage_path, old_name);
1825 newpath = e_path_to_physical (storage_path, new_name_in);
1826 g_free(storage_path);
1828 /* So do we care if this didn't work? Its just a cache? */
1829 if (rename (oldpath, newpath) == -1) {
1830 g_warning ("Could not rename message cache '%s' to '%s': %s: cache reset",
1831 oldpath, newpath, strerror (errno));
1838 imap_store->renaming = FALSE;
1841 static CamelFolderInfo *
1842 create_folder (CamelStore *store, const char *parent_name,
1843 const char *folder_name, CamelException *ex)
1845 CamelImapStore *imap_store = CAMEL_IMAP_STORE (store);
1846 char *full_name, *resp, *thisone, *parent_real, *real_name;
1847 CamelImapResponse *response;
1848 CamelException internal_ex;
1849 CamelFolderInfo *root = NULL;
1850 gboolean need_convert;
1853 if (!camel_disco_store_check_online (CAMEL_DISCO_STORE (store), ex))
1858 if (strchr (folder_name, imap_store->dir_sep)) {
1859 camel_exception_setv (ex, CAMEL_EXCEPTION_FOLDER_INVALID_PATH,
1860 _("The folder name \"%s\" is invalid because "
1861 "it containes the character \"%c\""),
1862 folder_name, imap_store->dir_sep);
1866 /* check if the parent allows inferiors */
1868 /* FIXME: use storesummary directly */
1869 parent_real = camel_imap_store_summary_full_from_path(imap_store->summary, parent_name);
1870 if (parent_real == NULL) {
1871 camel_exception_setv(ex, CAMEL_EXCEPTION_FOLDER_INVALID_STATE,
1872 _("Unknown parent folder: %s"), parent_name);
1876 need_convert = FALSE;
1877 response = camel_imap_command (imap_store, NULL, ex, "LIST \"\" %S",
1879 if (!response) /* whoa, this is bad */ {
1880 g_free(parent_real);
1884 /* FIXME: does not handle unexpected circumstances very well */
1885 for (i = 0; i < response->untagged->len; i++) {
1886 resp = response->untagged->pdata[i];
1888 if (!imap_parse_list_response (imap_store, resp, &flags, NULL, &thisone))
1891 if (strcmp (thisone, parent_name) == 0) {
1892 if (flags & CAMEL_FOLDER_NOINFERIORS)
1893 need_convert = TRUE;
1898 camel_imap_response_free (imap_store, response);
1900 camel_exception_init (&internal_ex);
1902 /* if not, check if we can delete it and recreate it */
1906 if (get_folder_status (imap_store, parent_name, "MESSAGES")) {
1907 camel_exception_set (ex, CAMEL_EXCEPTION_FOLDER_INVALID_STATE,
1908 _("The parent folder is not allowed to contain subfolders"));
1909 g_free(parent_real);
1913 /* delete the old parent and recreate it */
1914 delete_folder (store, parent_name, &internal_ex);
1915 if (camel_exception_is_set (&internal_ex)) {
1916 camel_exception_xfer (ex, &internal_ex);
1920 /* add the dirsep to the end of parent_name */
1921 name = g_strdup_printf ("%s%c", parent_real, imap_store->dir_sep);
1922 response = camel_imap_command (imap_store, NULL, ex, "CREATE %S",
1927 g_free(parent_real);
1930 camel_imap_response_free (imap_store, response);
1932 root = imap_build_folder_info(imap_store, parent_name);
1935 /* ok now we can create the folder */
1936 real_name = camel_imap_store_summary_path_to_full(imap_store->summary, folder_name, store->dir_sep);
1937 full_name = imap_concat (imap_store, parent_real, real_name);
1939 response = camel_imap_command (imap_store, NULL, ex, "CREATE %S", full_name);
1942 CamelImapStoreInfo *si;
1943 CamelFolderInfo *fi;
1945 camel_imap_response_free (imap_store, response);
1947 si = camel_imap_store_summary_add_from_full(imap_store->summary, full_name, store->dir_sep);
1948 camel_store_summary_save((CamelStoreSummary *)imap_store->summary);
1949 fi = imap_build_folder_info(imap_store, camel_store_info_path(imap_store->summary, si));
1956 camel_object_trigger_event (CAMEL_OBJECT (store), "folder_created", root);
1958 /* need to re-recreate the folder we just deleted */
1959 camel_object_trigger_event (CAMEL_OBJECT (store), "folder_created", root);
1960 camel_folder_info_free(root);
1965 g_free(parent_real);
1970 static CamelFolderInfo *
1971 parse_list_response_as_folder_info (CamelImapStore *imap_store,
1972 const char *response)
1974 CamelFolderInfo *fi;
1978 CamelImapStoreInfo *si;
1981 if (!imap_parse_list_response (imap_store, response, &flags, &sep, &dir))
1984 /* FIXME: should use imap_build_folder_info, note the differences with param setting tho */
1986 si = camel_imap_store_summary_add_from_full(imap_store->summary, dir, sep?sep:'/');
1987 newflags = (si->info.flags & CAMEL_STORE_INFO_FOLDER_SUBSCRIBED) | (flags & ~CAMEL_STORE_INFO_FOLDER_SUBSCRIBED);
1988 if (si->info.flags != newflags) {
1989 si->info.flags = newflags;
1990 camel_store_summary_touch((CamelStoreSummary *)imap_store->summary);
1993 fi = g_new0 (CamelFolderInfo, 1);
1995 fi->name = g_strdup(camel_store_info_name(imap_store->summary, si));
1996 fi->path = g_strdup_printf("/%s", camel_store_info_path(imap_store->summary, si));
1997 fi->full_name = g_strdup(fi->path+1);
1999 url = camel_url_new (imap_store->base_url, NULL);
2000 camel_url_set_path(url, fi->path);
2002 if (flags & CAMEL_FOLDER_NOSELECT || fi->name[0] == 0)
2003 camel_url_set_param (url, "noselect", "yes");
2004 fi->url = camel_url_to_string (url, 0);
2005 camel_url_free (url);
2007 /* FIXME: redundant */
2008 if (flags & CAMEL_IMAP_FOLDER_UNMARKED)
2009 fi->unread_message_count = -1;
2014 /* this is used when lsub doesn't provide very useful information */
2016 get_subscribed_folders (CamelImapStore *imap_store, const char *top, CamelException *ex)
2018 GPtrArray *names, *folders;
2019 int i, toplen = strlen (top);
2021 CamelImapResponse *response;
2022 CamelFolderInfo *fi;
2024 int haveinbox = FALSE;
2026 folders = g_ptr_array_new ();
2027 names = g_ptr_array_new ();
2028 for (i=0;(si = camel_store_summary_index((CamelStoreSummary *)imap_store->summary, i));i++) {
2029 if (si->flags & CAMEL_STORE_INFO_FOLDER_SUBSCRIBED) {
2030 g_ptr_array_add(names, (char *)camel_imap_store_info_full_name(imap_store->summary, si));
2031 haveinbox = haveinbox || strcasecmp(camel_imap_store_info_full_name(imap_store->summary, si), "INBOX") == 0;
2033 camel_store_summary_info_free((CamelStoreSummary *)imap_store->summary, si);
2037 g_ptr_array_add (names, "INBOX");
2039 for (i = 0; i < names->len; i++) {
2040 response = camel_imap_command (imap_store, NULL, ex,
2046 result = camel_imap_response_extract (imap_store, response, "LIST", NULL);
2048 camel_store_summary_remove_path((CamelStoreSummary *)imap_store->summary, names->pdata[i]);
2049 g_ptr_array_remove_index_fast (names, i);
2054 fi = parse_list_response_as_folder_info (imap_store, result);
2058 if (strncmp (top, fi->full_name, toplen) != 0) {
2059 camel_folder_info_free (fi);
2063 g_ptr_array_add (folders, fi);
2066 g_ptr_array_free (names, TRUE);
2071 static int imap_match_pattern(char dir_sep, const char *pattern, const char *name)
2081 } else if (p == '%') {
2087 } else if (p == '*') {
2093 return n == 0 && (p == '%' || p == 0);
2097 get_folders_online (CamelImapStore *imap_store, const char *pattern,
2098 GPtrArray *folders, gboolean lsub, CamelException *ex)
2100 CamelImapResponse *response;
2101 CamelFolderInfo *fi;
2104 GHashTable *present;
2107 response = camel_imap_command (imap_store, NULL, ex,
2108 "%s \"\" %S", lsub ? "LSUB" : "LIST",
2113 present = g_hash_table_new(g_str_hash, g_str_equal);
2114 for (i = 0; i < response->untagged->len; i++) {
2115 list = response->untagged->pdata[i];
2116 fi = parse_list_response_as_folder_info (imap_store, list);
2118 g_ptr_array_add(folders, fi);
2119 g_hash_table_insert(present, fi->full_name, fi);
2122 camel_imap_response_free (imap_store, response);
2124 /* update our summary to match the server */
2125 count = camel_store_summary_count((CamelStoreSummary *)imap_store->summary);
2126 for (i=0;i<count;i++) {
2127 si = camel_store_summary_index((CamelStoreSummary *)imap_store->summary, i);
2131 if (imap_match_pattern(((CamelStore *)imap_store)->dir_sep, pattern, camel_imap_store_info_full_name(imap_store->summary, si))) {
2132 if (g_hash_table_lookup(present, camel_store_info_path(imap_store->summary, si)) != NULL) {
2133 if (lsub && (si->flags & CAMEL_STORE_INFO_FOLDER_SUBSCRIBED) == 0) {
2134 si->flags |= CAMEL_STORE_INFO_FOLDER_SUBSCRIBED;
2135 camel_store_summary_touch((CamelStoreSummary *)imap_store->summary);
2139 if (si->flags & CAMEL_STORE_INFO_FOLDER_SUBSCRIBED) {
2140 si->flags &= ~CAMEL_STORE_INFO_FOLDER_SUBSCRIBED;
2141 camel_store_summary_touch((CamelStoreSummary *)imap_store->summary);
2144 camel_store_summary_remove((CamelStoreSummary *)imap_store->summary, si);
2150 camel_store_summary_info_free((CamelStoreSummary *)imap_store->summary, si);
2152 g_hash_table_destroy(present);
2157 dumpfi(CamelFolderInfo *fi)
2160 CamelFolderInfo *n = fi;
2172 printf("%-40s %-30s %*s\n", fi->path, fi->full_name, depth*2+strlen(fi->url), fi->url);
2181 get_folder_counts(CamelImapStore *imap_store, CamelFolderInfo *fi, CamelException *ex)
2184 CamelFolder *folder;
2186 /* non-recursive breath first search */
2188 q = g_slist_append(NULL, fi);
2192 q = g_slist_remove_link(q, q);
2195 /* ignore noselect folders, and check only inbox if we only check inbox */
2196 if ((fi->flags & CAMEL_FOLDER_NOSELECT) == 0
2197 && ( (imap_store->parameters & IMAP_PARAM_CHECK_ALL)
2198 || strcasecmp(fi->full_name, "inbox") == 0) ) {
2200 CAMEL_SERVICE_LOCK (imap_store, connect_lock);
2201 /* For the current folder, poke it to check for new
2202 * messages and then report that number, rather than
2203 * doing a STATUS command.
2205 if (imap_store->current_folder && strcmp(imap_store->current_folder->full_name, fi->full_name) == 0) {
2206 /* we bypass the folder locking otherwise we can deadlock. we use the command lock for
2207 any operations anyway so this is 'safe'. See comment above imap_store_refresh_folders() for info */
2208 CAMEL_FOLDER_CLASS (CAMEL_OBJECT_GET_CLASS(imap_store->current_folder))->refresh_info(imap_store->current_folder, ex);
2209 fi->unread_message_count = camel_folder_get_unread_message_count (imap_store->current_folder);
2211 fi->unread_message_count = get_folder_status (imap_store, fi->full_name, "UNSEEN");
2212 /* if we have this folder open, and the unread count has changed, update */
2213 folder = camel_object_bag_get(CAMEL_STORE(imap_store)->folders, fi->full_name);
2214 if (folder && fi->unread_message_count != camel_folder_get_unread_message_count(folder)) {
2215 CAMEL_FOLDER_CLASS (CAMEL_OBJECT_GET_CLASS(folder))->refresh_info(folder, ex);
2216 fi->unread_message_count = camel_folder_get_unread_message_count(folder);
2219 camel_object_unref(folder);
2223 CAMEL_SERVICE_UNLOCK (imap_store, connect_lock);
2225 /* since its cheap, get it if they're open */
2226 folder = camel_object_bag_get(CAMEL_STORE(imap_store)->folders, fi->full_name);
2228 fi->unread_message_count = camel_folder_get_unread_message_count(folder);
2229 camel_object_unref(folder);
2231 fi->unread_message_count = -1;
2235 q = g_slist_append(q, fi->child);
2241 /* imap needs to treat inbox case insensitive */
2242 /* we'll assume the names are normalised already */
2243 static guint folder_hash(const void *ap)
2247 if (strcasecmp(a, "INBOX") == 0)
2250 return g_str_hash(a);
2253 static int folder_eq(const void *ap, const void *bp)
2258 if (strcasecmp(a, "INBOX") == 0)
2260 if (strcasecmp(b, "INBOX") == 0)
2263 return g_str_equal(a, b);
2267 get_folders(CamelStore *store, const char *top, guint32 flags, CamelException *ex)
2269 CamelImapStore *imap_store = CAMEL_IMAP_STORE (store);
2273 GPtrArray *folders, *folders_out;
2274 CamelFolderInfo *fi;
2278 static int imap_max_depth = 0;
2280 if (!camel_imap_store_connected (imap_store, ex))
2283 /* allow megalomaniacs to override the max of 10 */
2284 if (imap_max_depth == 0) {
2285 name = getenv("CAMEL_IMAP_MAX_DEPTH");
2287 imap_max_depth = atoi (name);
2288 imap_max_depth = MIN (MAX (imap_max_depth, 0), 2);
2290 imap_max_depth = 10;
2293 infos = g_hash_table_new(folder_hash, folder_eq);
2295 /* get starting point & strip trailing '/' */
2297 if (imap_store->namespace) {
2298 top = imap_store->namespace;
2300 name = g_malloc(i+2);
2302 while (i>0 && name[i] == store->dir_sep)
2305 name = g_strdup("");
2307 name = camel_imap_store_summary_full_from_path(imap_store->summary, top);
2309 name = camel_imap_store_summary_path_to_full(imap_store->summary, top, store->dir_sep);
2312 d(printf("\n\nList '%s' %s\n", name, flags&CAMEL_STORE_FOLDER_INFO_RECURSIVE?"RECURSIVE":"NON-RECURSIVE"));
2314 folders_out = g_ptr_array_new();
2315 folders = g_ptr_array_new();
2317 /* first get working list of names */
2318 get_folders_online (imap_store, name[0]?name:"%", folders, flags & CAMEL_STORE_FOLDER_INFO_SUBSCRIBED, ex);
2319 if (camel_exception_is_set(ex))
2321 for (i=0; i<folders->len && !haveinbox; i++) {
2322 fi = folders->pdata[i];
2323 haveinbox = (strcasecmp(fi->full_name, "INBOX")) == 0;
2326 if (!haveinbox && top == imap_store->namespace) {
2327 get_folders_online (imap_store, "INBOX", folders,
2328 flags & CAMEL_STORE_FOLDER_INFO_SUBSCRIBED, ex);
2330 if (camel_exception_is_set (ex))
2334 for (i=0; i<folders->len; i++)
2335 p = g_slist_prepend(p, folders->pdata[i]);
2337 g_ptr_array_set_size(folders, 0);
2339 /* p is a reversed list of pending folders for the next level, q is the list of folders for this */
2341 GSList *q = g_slist_reverse(p);
2347 q = g_slist_remove_link(q, q);
2348 g_ptr_array_add(folders_out, fi);
2350 d(printf("Checking folder '%s'\n", fi->full_name));
2352 /* First if we're not recursive mode on the top level, and we know it has or doesn't
2353 or can't have children, no need to go further - a bit ugly */
2354 if ( top == imap_store->namespace
2355 && (flags & CAMEL_STORE_FOLDER_INFO_RECURSIVE) == 0
2356 && (fi->flags & (CAMEL_FOLDER_CHILDREN|CAMEL_IMAP_FOLDER_NOCHILDREN|CAMEL_FOLDER_NOINFERIORS)) != 0) {
2359 /* Otherwise, if this has (or might have) children, scan it */
2360 else if ( (fi->flags & (CAMEL_IMAP_FOLDER_NOCHILDREN|CAMEL_FOLDER_NOINFERIORS)) == 0
2361 || (fi->flags & CAMEL_FOLDER_CHILDREN) != 0) {
2364 real = camel_imap_store_summary_full_from_path(imap_store->summary, fi->full_name);
2365 n = imap_concat(imap_store, real?real:fi->full_name, "%");
2366 get_folders_online(imap_store, n, folders, flags & CAMEL_STORE_FOLDER_INFO_SUBSCRIBED, ex);
2370 if (folders->len > 0)
2371 fi->flags |= CAMEL_FOLDER_CHILDREN;
2373 for (i=0;i<folders->len;i++) {
2374 fi = folders->pdata[i];
2375 if (g_hash_table_lookup(infos, fi->full_name) == NULL) {
2376 g_hash_table_insert(infos, fi->full_name, fi);
2377 if ((flags & CAMEL_STORE_FOLDER_INFO_RECURSIVE) && depth<imap_max_depth)
2378 p = g_slist_prepend(p, fi);
2380 g_ptr_array_add(folders_out, fi);
2382 camel_folder_info_free(fi);
2385 g_ptr_array_set_size(folders, 0);
2391 g_ptr_array_free(folders, TRUE);
2392 g_hash_table_destroy(infos);
2397 g_ptr_array_free(folders, TRUE);
2398 g_ptr_array_free(folders_out, TRUE);
2399 g_hash_table_destroy(infos);
2405 static CamelFolderInfo *
2406 get_folder_info_online (CamelStore *store, const char *top, guint32 flags, CamelException *ex)
2408 CamelImapStore *imap_store = CAMEL_IMAP_STORE (store);
2409 CamelFolderInfo *tree;
2415 if ((flags & CAMEL_STORE_FOLDER_INFO_SUBSCRIBED)
2416 && !(imap_store->capabilities & IMAP_CAPABILITY_useful_lsub)
2417 && (imap_store->parameters & IMAP_PARAM_CHECK_ALL))
2418 folders = get_subscribed_folders(imap_store, top, ex);
2420 folders = get_folders(store, top, flags, ex);
2422 if (folders == NULL)
2425 tree = camel_folder_info_build(folders, top, '/', TRUE);
2426 g_ptr_array_free(folders, TRUE);
2428 if (!(flags & CAMEL_STORE_FOLDER_INFO_FAST))
2429 get_folder_counts(imap_store, tree, ex);
2432 camel_store_summary_save((CamelStoreSummary *)imap_store->summary);
2438 get_one_folder_offline (const char *physical_path, const char *path, gpointer data)
2440 GPtrArray *folders = data;
2441 CamelImapStore *imap_store = folders->pdata[0];
2442 CamelFolderInfo *fi;
2448 /* folder_info_build will insert parent nodes as necessary and mark
2449 * them as noselect, which is information we actually don't have at
2450 * the moment. So let it do the right thing by bailing out if it's
2451 * not a folder we're explicitly interested in.
2454 si = camel_store_summary_path((CamelStoreSummary *)imap_store->summary, path+1);
2456 if ((((CamelStore *)imap_store)->flags & CAMEL_STORE_SUBSCRIPTIONS) == 0
2457 || (si->flags & CAMEL_STORE_INFO_FOLDER_SUBSCRIBED)) {
2458 fi = imap_build_folder_info(imap_store, path+1);
2459 fi->flags = si->flags;
2460 if (si->flags & CAMEL_FOLDER_NOSELECT) {
2461 CamelURL *url = camel_url_new(fi->url, NULL);
2463 camel_url_set_param (url, "noselect", "yes");
2465 fi->url = camel_url_to_string (url, 0);
2466 camel_url_free (url);
2468 g_ptr_array_add (folders, fi);
2470 camel_store_summary_info_free((CamelStoreSummary *)imap_store->summary, si);
2476 static CamelFolderInfo *
2477 get_folder_info_offline (CamelStore *store, const char *top,
2478 guint32 flags, CamelException *ex)
2480 CamelImapStore *imap_store = CAMEL_IMAP_STORE (store);
2481 CamelFolderInfo *fi;
2485 if (!imap_store->connected &&
2486 !camel_service_connect (CAMEL_SERVICE (store), ex))
2489 if ((store->flags & CAMEL_STORE_SUBSCRIPTIONS) &&
2490 !(flags & CAMEL_STORE_FOLDER_INFO_SUBSCRIBED)) {
2491 camel_disco_store_check_online (CAMEL_DISCO_STORE (store), ex);
2495 /* FIXME: obey other flags */
2497 folders = g_ptr_array_new ();
2499 /* A kludge to avoid having to pass a struct to the callback */
2500 g_ptr_array_add (folders, imap_store);
2501 storage_path = g_strdup_printf("%s/folders", imap_store->storage_path);
2502 if (!e_path_find_folders (storage_path, get_one_folder_offline, folders)) {
2503 camel_disco_store_check_online (CAMEL_DISCO_STORE (imap_store), ex);
2506 g_ptr_array_remove_index_fast (folders, 0);
2507 fi = camel_folder_info_build (folders, "", '/', TRUE);
2509 g_free(storage_path);
2511 g_ptr_array_free (folders, TRUE);
2516 folder_subscribed (CamelStore *store, const char *folder_name)
2518 CamelImapStore *imap_store = CAMEL_IMAP_STORE (store);
2522 si = camel_store_summary_path((CamelStoreSummary *)imap_store->summary, folder_name);
2524 truth = (si->flags & CAMEL_STORE_INFO_FOLDER_SUBSCRIBED) != 0;
2525 camel_store_summary_info_free((CamelStoreSummary *)imap_store->summary, si);
2531 /* Note: folder_name must match a folder as listed with get_folder_info() -> full_name */
2533 subscribe_folder (CamelStore *store, const char *folder_name,
2536 CamelImapStore *imap_store = CAMEL_IMAP_STORE (store);
2537 CamelImapResponse *response;
2538 CamelFolderInfo *fi;
2541 if (!camel_disco_store_check_online (CAMEL_DISCO_STORE (store), ex))
2543 if (!camel_imap_store_connected (imap_store, ex))
2546 response = camel_imap_command (imap_store, NULL, ex,
2547 "SUBSCRIBE %F", folder_name);
2550 camel_imap_response_free (imap_store, response);
2552 si = camel_store_summary_path((CamelStoreSummary *)imap_store->summary, folder_name);
2554 if ((si->flags & CAMEL_STORE_INFO_FOLDER_SUBSCRIBED) == 0) {
2555 si->flags |= CAMEL_STORE_INFO_FOLDER_SUBSCRIBED;
2556 camel_store_summary_touch((CamelStoreSummary *)imap_store->summary);
2557 camel_store_summary_save((CamelStoreSummary *)imap_store->summary);
2559 camel_store_summary_info_free((CamelStoreSummary *)imap_store->summary, si);
2562 if (imap_store->renaming) {
2563 /* we don't need to emit a "folder_subscribed" signal
2564 if we are in the process of renaming folders, so we
2569 fi = imap_build_folder_info(imap_store, folder_name);
2570 camel_object_trigger_event (CAMEL_OBJECT (store), "folder_subscribed", fi);
2571 camel_folder_info_free (fi);
2575 unsubscribe_folder (CamelStore *store, const char *folder_name,
2578 CamelImapStore *imap_store = CAMEL_IMAP_STORE (store);
2579 CamelImapResponse *response;
2581 if (!camel_disco_store_check_online (CAMEL_DISCO_STORE (store), ex))
2583 if (!camel_imap_store_connected (imap_store, ex))
2586 response = camel_imap_command (imap_store, NULL, ex,
2587 "UNSUBSCRIBE %F", folder_name);
2590 camel_imap_response_free (imap_store, response);
2592 imap_folder_effectively_unsubscribed (imap_store, folder_name, ex);
2597 folder_flags_have_changed (CamelFolder *folder)
2599 CamelMessageInfo *info;
2602 max = camel_folder_summary_count (folder->summary);
2603 for (i = 0; i < max; i++) {
2604 info = camel_folder_summary_index (folder->summary, i);
2607 if (info->flags & CAMEL_MESSAGE_FOLDER_FLAGGED) {
2618 camel_imap_store_connected (CamelImapStore *store, CamelException *ex)
2620 if (store->istream == NULL || !store->connected)
2621 return camel_service_connect (CAMEL_SERVICE (store), ex);
2626 /* FIXME: please god, when will the hurting stop? Thus function is so
2627 fucking broken it's not even funny. */
2629 camel_imap_store_readline (CamelImapStore *store, char **dest, CamelException *ex)
2631 CamelStreamBuffer *stream;
2636 g_return_val_if_fail (CAMEL_IS_IMAP_STORE (store), -1);
2637 g_return_val_if_fail (dest, -1);
2641 /* Check for connectedness. Failed (or cancelled) operations will
2642 * close the connection. We can't expect a read to have any
2643 * meaning if we reconnect, so always set an exception.
2646 if (!camel_imap_store_connected (store, ex)) {
2647 camel_exception_set (ex, CAMEL_EXCEPTION_SERVICE_NOT_CONNECTED,
2648 g_strerror (errno));
2652 stream = CAMEL_STREAM_BUFFER (store->istream);
2654 ba = g_byte_array_new ();
2655 while ((nread = camel_stream_buffer_gets (stream, linebuf, sizeof (linebuf))) > 0) {
2656 g_byte_array_append (ba, linebuf, nread);
2657 if (linebuf[nread - 1] == '\n')
2663 camel_exception_set (ex, CAMEL_EXCEPTION_USER_CANCEL, _("Operation cancelled"));
2665 camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
2666 _("Server unexpectedly disconnected: %s"),
2667 g_strerror (errno));
2669 camel_service_disconnect (CAMEL_SERVICE (store), FALSE, NULL);
2670 g_byte_array_free (ba, TRUE);
2674 if (camel_verbose_debug) {
2675 fprintf (stderr, "received: ");
2676 fwrite (ba->data, 1, ba->len, stderr);
2679 /* camel-imap-command.c:imap_read_untagged expects the CRLFs
2680 to be stripped off and be nul-terminated *sigh* */
2681 nread = ba->len - 1;
2682 ba->data[nread] = '\0';
2683 if (ba->data[nread - 1] == '\r') {
2684 ba->data[nread - 1] = '\0';
2689 g_byte_array_free (ba, FALSE);