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, 2003 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 Lesser 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 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.
36 #include <glib/gi18n-lib.h>
37 #include <glib/gstdio.h>
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"
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"
68 /* Specified in RFC 2060 */
69 #define IMAP_PORT "143"
70 #define IMAPS_PORT "993"
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).
77 #define strtok_r(s,sep,lasts) (*(lasts)=strtok((s),(sep)))
80 static CamelDiscoStoreClass *parent_class = NULL;
82 static char imap_tag_prefix = 'A';
84 static void construct (CamelService *service, CamelSession *session,
85 CamelProvider *provider, CamelURL *url,
88 static int imap_setv (CamelObject *object, CamelException *ex, CamelArgV *args);
89 static int imap_getv (CamelObject *object, CamelException *ex, CamelArgGetV *args);
91 static char *imap_get_name (CamelService *service, gboolean brief);
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);
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,
114 static CamelFolderInfo *get_folder_info_offline (CamelStore *store,
118 static gboolean folder_subscribed (CamelStore *store, const char *folder_name);
119 static void subscribe_folder (CamelStore *store, const char *folder_name,
121 static void unsubscribe_folder (CamelStore *store, const char *folder_name,
124 static void get_folders_sync(CamelImapStore *imap_store, const char *pattern, CamelException *ex);
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);
132 camel_imap_store_class_init (CamelImapStoreClass *camel_imap_store_class)
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);
143 parent_class = CAMEL_DISCO_STORE_CLASS (camel_type_get_global_classfuncs (camel_disco_store_get_type ()));
145 /* virtual method overload */
146 camel_object_class->setv = imap_setv;
147 camel_object_class->getv = imap_getv;
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;
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;
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;
180 free_key (gpointer key, gpointer value, gpointer user_data)
187 camel_imap_store_finalize (CamelObject *object)
189 CamelImapStore *imap_store = CAMEL_IMAP_STORE (object);
190 CamelDiscoStore *disco = CAMEL_DISCO_STORE (object);
192 /* This frees current_folder, folders, authtypes, streams, and namespace. */
193 camel_service_disconnect((CamelService *)imap_store, TRUE, NULL);
195 if (imap_store->summary) {
196 camel_store_summary_save((CamelStoreSummary *)imap_store->summary);
197 camel_object_unref(imap_store->summary);
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);
206 camel_object_unref (disco->diary);
210 if (imap_store->custom_headers)
211 g_free (imap_store->custom_headers);
215 camel_imap_store_init (gpointer object, gpointer klass)
217 CamelImapStore *imap_store = CAMEL_IMAP_STORE (object);
219 imap_store->istream = NULL;
220 imap_store->ostream = NULL;
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;
228 imap_store->tag_prefix = imap_tag_prefix++;
229 if (imap_tag_prefix > 'Z')
230 imap_tag_prefix = 'A';
234 camel_imap_store_get_type (void)
236 static CamelType camel_imap_store_type = CAMEL_INVALID_TYPE;
238 if (camel_imap_store_type == CAMEL_INVALID_TYPE) {
239 camel_imap_store_type =
240 camel_type_register (CAMEL_DISCO_STORE_TYPE,
242 sizeof (CamelImapStore),
243 sizeof (CamelImapStoreClass),
244 (CamelObjectClassInitFunc) camel_imap_store_class_init,
246 (CamelObjectInitFunc) camel_imap_store_init,
247 (CamelObjectFinalizeFunc) camel_imap_store_finalize);
250 return camel_imap_store_type;
254 construct (CamelService *service, CamelSession *session,
255 CamelProvider *provider, CamelURL *url,
258 CamelImapStore *imap_store = CAMEL_IMAP_STORE (service);
259 CamelStore *store = CAMEL_STORE (service);
260 CamelDiscoStore *disco_store = CAMEL_DISCO_STORE (service);
262 CamelURL *summary_url;
264 CAMEL_SERVICE_CLASS (parent_class)->construct (service, session, provider, url, ex);
265 if (camel_exception_is_set (ex))
268 imap_store->storage_path = camel_session_get_storage_path (session, service, ex);
269 if (!imap_store->storage_path)
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));
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"));
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;
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;
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;
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"));
308 path = g_strdup_printf ("%s/journal", imap_store->storage_path);
309 disco_store->diary = camel_disco_diary_new (disco_store, path, ex);
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;
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);
328 imap_store->namespace = g_strdup(is->namespace->full_name);
329 imap_store->dir_sep = is->namespace->sep;
333 imap_store->capabilities = is->capabilities;
334 imap_set_server_level(imap_store);
339 imap_setv (CamelObject *object, CamelException *ex, CamelArgV *args)
341 CamelImapStore *store = (CamelImapStore *) object;
345 for (i = 0; i < args->argc; i++) {
346 tag = args->argv[i].tag;
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)
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;*/
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);
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;*/
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... */
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... */
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);
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);
397 /* let our parent know that we've handled this arg */
398 camel_argv_ignore (args, i);
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. */
405 return CAMEL_OBJECT_CLASS (parent_class)->setv (object, ex, args);
409 imap_getv (CamelObject *object, CamelException *ex, CamelArgGetV *args)
411 CamelImapStore *store = (CamelImapStore *) object;
415 for (i = 0; i < args->argc; i++) {
416 tag = args->argv[i].tag;
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)
424 case CAMEL_IMAP_STORE_NAMESPACE:
425 *args->argv[i].ca_str = store->namespace;
427 case CAMEL_IMAP_STORE_OVERRIDE_NAMESPACE:
428 *args->argv[i].ca_int = store->parameters & IMAP_PARAM_OVERRIDE_NAMESPACE ? TRUE : FALSE;
430 case CAMEL_IMAP_STORE_CHECK_ALL:
431 *args->argv[i].ca_int = store->parameters & IMAP_PARAM_CHECK_ALL ? TRUE : FALSE;
433 case CAMEL_IMAP_STORE_FILTER_INBOX:
434 *args->argv[i].ca_int = store->parameters & IMAP_PARAM_FILTER_INBOX ? TRUE : FALSE;
436 case CAMEL_IMAP_STORE_FILTER_JUNK:
437 *args->argv[i].ca_int = store->parameters & IMAP_PARAM_FILTER_JUNK ? TRUE : FALSE;
439 case CAMEL_IMAP_STORE_FILTER_JUNK_INBOX:
440 *args->argv[i].ca_int = store->parameters & IMAP_PARAM_FILTER_JUNK_INBOX ? TRUE : FALSE;
448 return CAMEL_OBJECT_CLASS (parent_class)->getv (object, ex, args);
452 imap_get_name (CamelService *service, gboolean brief)
455 return g_strdup_printf (_("IMAP server %s"), service->url->host);
457 return g_strdup_printf (_("IMAP service for %s on %s"),
458 service->url->user, service->url->host);
462 imap_set_server_level (CamelImapStore *store)
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;
470 store->server_level = IMAP_LEVEL_UNKNOWN;
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 },
491 parse_capability(CamelImapStore *store, char *capa)
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,
500 GINT_TO_POINTER (1));
503 for (i = 0; capabilities[i].name; i++) {
504 if (g_ascii_strcasecmp (capa, capabilities[i].name) == 0) {
505 store->capabilities |= capabilities[i].flag;
513 imap_get_capability (CamelService *service, CamelException *ex)
515 CamelImapStore *store = CAMEL_IMAP_STORE (service);
516 CamelImapResponse *response;
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");
526 result = camel_imap_response_extract (store, response, "CAPABILITY ", ex);
530 /* Skip over "* CAPABILITY ". */
531 parse_capability(store, result+13);
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);
544 imap_set_server_level (store);
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);
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)
567 connect_to_server (CamelService *service, struct addrinfo *ai, int ssl_mode, CamelException *ex)
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;
577 if (ssl_mode != MODE_CLEAR) {
579 if (ssl_mode == MODE_TLS) {
580 tcp_stream = camel_tcp_stream_ssl_new_raw (service->session, service->url->host, STARTTLS_FLAGS);
582 tcp_stream = camel_tcp_stream_ssl_new (service->session, service->url->host, SSL_PORT_FLAGS);
585 camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
586 _("Could not connect to %s: %s"),
587 service->url->host, _("SSL unavailable"));
590 #endif /* HAVE_SSL */
592 tcp_stream = camel_tcp_stream_raw_new ();
595 if (camel_tcp_stream_connect ((CamelTcpStream *) tcp_stream, ai) == -1) {
597 camel_exception_set (ex, CAMEL_EXCEPTION_USER_CANCEL,
598 _("Connection cancelled"));
600 camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
601 _("Could not connect to %s: %s"),
605 camel_object_unref (tcp_stream);
610 store->ostream = tcp_stream;
611 store->istream = camel_stream_buffer_new (tcp_stream, CAMEL_STREAM_BUFFER_READ);
613 store->connected = TRUE;
614 store->preauthed = FALSE;
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);
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);
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;
634 if (store->ostream) {
635 camel_object_unref (store->ostream);
636 store->ostream = NULL;
639 store->connected = FALSE;
644 if (!strncmp(buf, "* PREAUTH", 9))
645 store->preauthed = TRUE;
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.
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
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
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;
684 if (store->ostream) {
685 camel_object_unref (store->ostream);
686 store->ostream = NULL;
689 store->connected = FALSE;
694 store->capabilities &= ~IMAP_CAPABILITY_IMAP4REV1;
695 store->server_level = IMAP_LEVEL_IMAP4;
698 if (ssl_mode != MODE_TLS) {
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"));
712 /* as soon as we send a STARTTLS command, all hope is lost of a clean QUIT if problems arise */
715 response = camel_imap_command (store, NULL, ex, "STARTTLS");
717 camel_object_unref (store->istream);
718 camel_object_unref (store->ostream);
719 store->istream = store->ostream = NULL;
723 camel_imap_response_free_without_processing (store, response);
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"));
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"));
737 #endif /* HAVE_SSL */
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;
747 if (store->ostream) {
748 camel_object_unref (store->ostream);
749 store->ostream = NULL;
752 store->connected = FALSE;
757 if (store->capabilities & IMAP_CAPABILITY_LOGINDISABLED ) {
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"));
768 if (clean_quit && store->connected) {
769 /* try to disconnect cleanly */
770 response = camel_imap_command (store, NULL, ex, "LOGOUT");
772 camel_imap_response_free_without_processing (store, response);
775 if (store->istream) {
776 camel_object_unref (store->istream);
777 store->istream = NULL;
780 if (store->ostream) {
781 camel_object_unref (store->ostream);
782 store->ostream = NULL;
785 store->connected = FALSE;
792 /* Using custom commands to connect to IMAP servers is not supported on Win32 */
795 connect_to_server_process (CamelService *service, const char *cmd, CamelException *ex)
797 CamelImapStore *store = (CamelImapStore *) service;
798 CamelStream *cmd_stream;
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);
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);
822 /* Now do %h, %u, etc. substitution in cmd */
823 buf = cmd_copy = g_strdup(cmd);
825 full_cmd = g_strdup("");
833 pc = strchr(buf, '%');
836 tmp = g_strdup_printf("%s%s", full_cmd, buf);
848 var = service->url->host;
851 var = service->url->user;
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, '%');
860 tmp = g_strdup_printf("%s%.*s%s", full_cmd, len, buf, var);
868 cmd_stream = camel_stream_process_new ();
870 ret = camel_stream_process_connect (CAMEL_STREAM_PROCESS(cmd_stream),
871 full_cmd, (const char **)child_env);
874 g_free(child_env[--i]);
878 camel_exception_set (ex, CAMEL_EXCEPTION_USER_CANCEL,
879 _("Connection cancelled"));
881 camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
882 _("Could not connect with command \"%s\": %s"),
883 full_cmd, g_strerror (errno));
885 camel_object_unref (cmd_stream);
891 store->ostream = cmd_stream;
892 store->istream = camel_stream_buffer_new (cmd_stream, CAMEL_STREAM_BUFFER_READ);
894 store->connected = TRUE;
895 store->preauthed = FALSE;
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;
905 if (store->ostream) {
906 camel_object_unref (store->ostream);
907 store->ostream = NULL;
910 store->connected = FALSE;
913 if (!strncmp(buf, "* PREAUTH", 9))
914 store->preauthed = TRUE;
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;
924 if (store->ostream) {
925 camel_object_unref (store->ostream);
926 store->ostream = NULL;
929 store->connected = FALSE;
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 },
953 connect_to_server_wrapper (CamelService *service, CamelException *ex)
955 const char *ssl_mode;
956 struct addrinfo hints, *ai;
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);
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))
972 mode = ssl_options[i].mode;
973 serv = ssl_options[i].serv;
974 port = ssl_options[i].port;
981 if (service->url->port) {
982 serv = g_alloca (16);
983 sprintf (serv, "%d", service->url->port);
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);
999 ret = connect_to_server (service, ai, mode, ex);
1001 camel_freeaddrinfo (ai);
1006 extern CamelServiceAuthType camel_imap_password_authtype;
1009 query_auth_types (CamelService *service, CamelException *ex)
1011 CamelImapStore *store = CAMEL_IMAP_STORE (service);
1012 CamelServiceAuthType *authtype;
1013 GList *sasl_types, *t, *next;
1016 if (!camel_disco_store_check_online (CAMEL_DISCO_STORE (store), ex))
1019 CAMEL_SERVICE_REC_LOCK (store, connect_lock);
1020 connected = store->istream != NULL && store->connected;
1022 connected = connect_to_server_wrapper (service, ex);
1023 CAMEL_SERVICE_REC_UNLOCK (store, connect_lock);
1027 sasl_types = camel_sasl_authtype_list (FALSE);
1028 for (t = sasl_types; t; t = next) {
1032 if (!g_hash_table_lookup (store->authtypes, authtype->authproto)) {
1033 sasl_types = g_list_remove_link (sasl_types, t);
1038 return g_list_prepend (sasl_types, &camel_imap_password_authtype);
1041 /* folder_name is path name */
1042 static CamelFolderInfo *
1043 imap_build_folder_info(CamelImapStore *imap_store, const char *folder_name)
1047 CamelFolderInfo *fi;
1049 fi = g_malloc0(sizeof(*fi));
1051 fi->full_name = g_strdup(folder_name);
1055 url = camel_url_new (imap_store->base_url, NULL);
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, '/');
1062 name = fi->full_name;
1065 if (!g_ascii_strcasecmp (fi->full_name, "INBOX"))
1066 fi->name = g_strdup (_("Inbox"));
1068 fi->name = g_strdup (name);
1074 imap_folder_effectively_unsubscribed(CamelImapStore *imap_store,
1075 const char *folder_name, CamelException *ex)
1077 CamelFolderInfo *fi;
1080 si = camel_store_summary_path((CamelStoreSummary *)imap_store->summary, folder_name);
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);
1087 camel_store_summary_info_free((CamelStoreSummary *)imap_store->summary, si);
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
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);
1104 imap_forget_folder (CamelImapStore *imap_store, const char *folder_name, CamelException *ex)
1106 CamelFolderSummary *summary;
1107 CamelImapMessageCache *cache;
1108 char *summary_file, *state_file;
1110 char *folder_dir, *storage_path;
1111 CamelFolderInfo *fi;
1114 name = strrchr (folder_name, imap_store->dir_sep);
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);
1128 summary_file = g_strdup_printf ("%s/summary", folder_dir);
1129 summary = camel_imap_summary_new (NULL, summary_file);
1131 g_free (summary_file);
1132 g_free (folder_dir);
1135 camel_object_unref (summary);
1137 g_unlink (summary_file);
1138 g_free (summary_file);
1140 summary_file = g_strdup_printf ("%s/summary-meta", folder_dir);
1141 summary = camel_imap_summary_new (NULL, summary_file);
1143 g_free (summary_file);
1144 g_free (folder_dir);
1148 cache = camel_imap_message_cache_new (folder_dir, summary, ex);
1150 camel_imap_message_cache_clear (cache);
1152 camel_object_unref (cache);
1153 camel_object_unref (summary);
1155 g_unlink (summary_file);
1156 g_free (summary_file);
1158 journal_file = g_strdup_printf ("%s/journal", folder_dir);
1159 g_unlink (journal_file);
1160 g_free (journal_file);
1162 state_file = g_strdup_printf ("%s/cmeta", folder_dir);
1163 g_unlink (state_file);
1164 g_free (state_file);
1166 state_file = g_strdup_printf("%s/subfolders", folder_dir);
1167 g_rmdir(state_file);
1170 g_rmdir (folder_dir);
1171 g_free (folder_dir);
1175 camel_store_summary_remove_path((CamelStoreSummary *)imap_store->summary, folder_name);
1176 camel_store_summary_save((CamelStoreSummary *)imap_store->summary);
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);
1184 imap_check_folder_still_extant (CamelImapStore *imap_store, const char *full_name,
1187 CamelImapResponse *response;
1189 response = camel_imap_command (imap_store, NULL, ex, "LIST \"\" %F",
1193 gboolean stillthere = response->untagged->len != 0;
1195 camel_imap_response_free_without_processing (imap_store, response);
1200 /* if the command was rejected, there must be some other error,
1201 assume it worked so we dont blow away the folder unecessarily */
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 */
1215 imap_store_refresh_folders (CamelImapStore *store, CamelException *ex)
1220 folders = camel_object_bag_list(CAMEL_STORE (store)->folders);
1222 for (i = 0; i <folders->len; i++) {
1223 CamelFolder *folder = folders->pdata[i];
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);
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);
1235 if (camel_exception_is_set (ex) &&
1236 imap_check_folder_still_extant (store, folder->full_name, ex) == FALSE) {
1239 /* the folder was deleted (may happen when we come back online
1240 * after being offline */
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);
1248 camel_object_unref(folder);
1251 g_ptr_array_free (folders, TRUE);
1256 try_auth (CamelImapStore *store, const char *mech, CamelException *ex)
1259 CamelImapResponse *response;
1263 response = camel_imap_command (store, NULL, ex, "AUTHENTICATE %s", mech);
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);
1273 sasl_resp = camel_sasl_challenge_base64 (sasl, imap_next_word (resp), ex);
1275 if (!sasl_resp || camel_exception_is_set (ex))
1276 goto break_and_lose;
1278 response = camel_imap_command_continuation (store, sasl_resp, strlen (sasl_resp), ex);
1284 resp = camel_imap_response_extract_continuation (store, response, NULL);
1286 /* Oops. SASL claims we're done, but the IMAP server
1287 * doesn't think so...
1293 camel_object_unref (sasl);
1298 /* Get the server out of "waiting for continuation data" mode. */
1299 response = camel_imap_command_continuation (store, "*", 1, NULL);
1301 camel_imap_response_free (store, response);
1304 if (!camel_exception_is_set (ex)) {
1305 camel_exception_set (ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE,
1306 _("Bad authentication response from server."));
1309 camel_object_unref (sasl);
1315 imap_auth_loop (CamelService *service, CamelException *ex)
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;
1325 auth_domain = camel_url_get_param (service->url, "auth-domain");
1327 if (store->preauthed) {
1328 if (camel_verbose_debug)
1329 fprintf(stderr, "Server %s has preauthenticated us.\n",
1330 service->url->host);
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"),
1340 service->url->authmech);
1344 authtype = camel_sasl_authtype (service->url->authmech);
1346 camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE,
1347 _("No support for authentication type %s"),
1348 service->url->authmech);
1352 if (!authtype->need_password) {
1353 authenticated = try_auth (store, authtype->authproto, ex);
1359 while (!authenticated) {
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;
1367 if (!service->url->passwd) {
1370 prompt = g_strdup_printf (_("%sPlease enter the IMAP "
1371 "password for %s@%s"),
1372 errbuf ? errbuf : "",
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);
1382 if (!service->url->passwd) {
1383 camel_exception_set (ex, CAMEL_EXCEPTION_USER_CANCEL,
1384 _("You did not enter a password."));
1389 if (!store->connected) {
1390 /* Some servers (eg, courier) will disconnect on
1391 * a bad password. So reconnect here.
1393 if (!connect_to_server_wrapper (service, ex))
1398 authenticated = try_auth (store, authtype->authproto, ex);
1400 response = camel_imap_command (store, NULL, ex,
1403 service->url->passwd);
1405 camel_imap_response_free (store, response);
1406 authenticated = TRUE;
1409 if (!authenticated) {
1410 if (camel_exception_get_id(ex) == CAMEL_EXCEPTION_USER_CANCEL)
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);
1424 can_work_offline (CamelDiscoStore *disco_store)
1426 CamelImapStore *store = CAMEL_IMAP_STORE (disco_store);
1428 return camel_store_summary_count((CamelStoreSummary *)store->summary) != 0;
1432 imap_connect_online (CamelService *service, CamelException *ex)
1434 CamelImapStore *store = CAMEL_IMAP_STORE (service);
1435 CamelImapResponse *response;
1436 /*struct _namespaces *namespaces;*/
1437 char *result, *name;
1439 CamelImapStoreNamespace *ns;
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);
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");
1456 result = camel_imap_response_extract (store, response, "NAMESPACE", ex);
1462 namespaces = imap_parse_namespace_response (result);
1463 imap_namespaces_destroy (namespaces);
1467 name = camel_strstrcase (result, "NAMESPACE ((");
1472 store->namespace = imap_parse_string ((const char **) &name, &len);
1473 if (name && *name++ == ' ') {
1474 sep = imap_parse_string ((const char **) &name, &len);
1476 store->dir_sep = *sep;
1484 if (!store->namespace)
1485 store->namespace = g_strdup ("");
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.
1492 response = camel_imap_command (store, NULL, ex,
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 "").
1500 response = camel_imap_command (store, NULL, ex,
1507 result = camel_imap_response_extract (store, response, "LIST", NULL);
1509 imap_parse_list_response (store, result, NULL, &store->dir_sep, NULL);
1512 if (!store->dir_sep) {
1513 store->dir_sep = '/'; /* Guess */
1517 /* canonicalize the namespace to end with dir_sep */
1518 len = strlen (store->namespace);
1519 if (len && store->namespace[len - 1] != store->dir_sep) {
1522 tmp = g_strdup_printf ("%s%c", store->namespace, store->dir_sep);
1523 g_free (store->namespace);
1524 store->namespace = tmp;
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);
1530 if ((store->parameters & IMAP_PARAM_SUBSCRIPTIONS)
1531 && camel_store_summary_count((CamelStoreSummary *)store->summary) == 0) {
1535 get_folders_sync(store, store->namespace, ex);
1536 if (camel_exception_is_set(ex))
1538 pattern = imap_concat(store, store->namespace, "*");
1539 get_folders_sync(store, pattern, ex);
1541 if (camel_exception_is_set(ex))
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);
1552 camel_store_summary_info_free((CamelStoreSummary *)store->summary, si);
1553 if (camel_exception_is_set(ex))
1555 get_folders_sync(store, "INBOX", ex);
1557 store->refresh_stamp = time(0);
1562 /* save any changes we had */
1563 camel_store_summary_save((CamelStoreSummary *)store->summary);
1565 CAMEL_SERVICE_REC_UNLOCK (store, connect_lock);
1567 if (camel_exception_is_set (ex))
1568 camel_service_disconnect (service, TRUE, NULL);
1570 return !camel_exception_is_set (ex);
1574 imap_connect_offline (CamelService *service, CamelException *ex)
1576 CamelImapStore *store = CAMEL_IMAP_STORE (service);
1577 CamelDiscoStore *disco_store = CAMEL_DISCO_STORE (service);
1579 if (!disco_store->diary)
1582 store->connected = !camel_exception_is_set (ex);
1583 return store->connected;
1587 imap_disconnect_offline (CamelService *service, gboolean clean, CamelException *ex)
1589 CamelImapStore *store = CAMEL_IMAP_STORE (service);
1591 if (store->istream) {
1592 camel_stream_close(store->istream);
1593 camel_object_unref(store->istream);
1594 store->istream = NULL;
1597 if (store->ostream) {
1598 camel_stream_close(store->ostream);
1599 camel_object_unref(store->ostream);
1600 store->ostream = NULL;
1603 store->connected = FALSE;
1604 if (store->current_folder) {
1605 camel_object_unref (store->current_folder);
1606 store->current_folder = NULL;
1609 if (store->authtypes) {
1610 g_hash_table_foreach_remove (store->authtypes,
1612 g_hash_table_destroy (store->authtypes);
1613 store->authtypes = NULL;
1616 if (store->namespace && !(store->parameters & IMAP_PARAM_OVERRIDE_NAMESPACE)) {
1617 g_free (store->namespace);
1618 store->namespace = NULL;
1625 imap_disconnect_online (CamelService *service, gboolean clean, CamelException *ex)
1627 CamelImapStore *store = CAMEL_IMAP_STORE (service);
1628 CamelImapResponse *response;
1630 if (store->connected && clean) {
1631 response = camel_imap_command (store, NULL, NULL, "LOGOUT");
1632 camel_imap_response_free (store, response);
1635 imap_disconnect_offline (service, clean, ex);
1642 imap_summary_is_dirty (CamelFolderSummary *summary)
1644 CamelImapMessageInfo *info;
1648 max = camel_folder_summary_count (summary);
1649 for (i = 0; i < max && !found; i++) {
1650 info = (CamelImapMessageInfo *)camel_folder_summary_index (summary, i);
1652 found = info->info.flags & CAMEL_MESSAGE_FOLDER_FLAGGED;
1653 camel_message_info_free(info);
1661 imap_noop (CamelStore *store, CamelException *ex)
1663 CamelImapStore *imap_store = (CamelImapStore *) store;
1664 CamelImapResponse *response;
1665 CamelFolder *current_folder;
1667 CAMEL_SERVICE_REC_LOCK (imap_store, connect_lock);
1669 if (!camel_imap_store_connected(imap_store, ex))
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);
1677 response = camel_imap_command (imap_store, NULL, ex, "NOOP");
1679 camel_imap_response_free (imap_store, response);
1682 CAMEL_SERVICE_REC_UNLOCK (imap_store, connect_lock);
1685 static CamelFolder *
1686 imap_get_trash(CamelStore *store, CamelException *ex)
1688 CamelFolder *folder = CAMEL_STORE_CLASS(parent_class)->get_trash(store, ex);
1691 char *state = g_build_filename(((CamelImapStore *)store)->storage_path, "system", "Trash.cmeta", NULL);
1693 camel_object_set(folder, NULL, CAMEL_OBJECT_STATE_FILE, state, NULL);
1696 camel_object_state_read(folder);
1702 static CamelFolder *
1703 imap_get_junk(CamelStore *store, CamelException *ex)
1705 CamelFolder *folder = CAMEL_STORE_CLASS(parent_class)->get_junk(store, ex);
1708 char *state = g_build_filename(((CamelImapStore *)store)->storage_path, "system", "Junk.cmeta", NULL);
1710 camel_object_set(folder, NULL, CAMEL_OBJECT_STATE_FILE, state, NULL);
1713 camel_object_state_read(folder);
1720 hash_folder_name (gconstpointer key)
1722 if (g_ascii_strcasecmp (key, "INBOX") == 0)
1723 return g_str_hash ("INBOX");
1725 return g_str_hash (key);
1729 compare_folder_name (gconstpointer a, gconstpointer b)
1731 gconstpointer aname = a, bname = b;
1733 if (g_ascii_strcasecmp (a, "INBOX") == 0)
1735 if (g_ascii_strcasecmp (b, "INBOX") == 0)
1737 return g_str_equal (aname, bname);
1740 struct imap_status_item {
1741 struct imap_status_item *next;
1747 imap_status_item_free (struct imap_status_item *items)
1749 struct imap_status_item *next;
1751 while (items != NULL) {
1753 g_free (items->name);
1759 static struct imap_status_item *
1760 get_folder_status (CamelImapStore *imap_store, const char *folder_name, const char *type)
1762 struct imap_status_item *items, *item, *tail;
1763 CamelImapResponse *response;
1764 char *status, *name, *p;
1766 /* FIXME: we assume the server is STATUS-capable */
1768 response = camel_imap_command (imap_store, NULL, NULL,
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);
1781 camel_exception_clear (&ex);
1785 if (!(status = camel_imap_response_extract (imap_store, response, "STATUS", NULL)))
1788 p = status + strlen ("* STATUS ");
1792 /* skip past the mailbox string */
1795 while (*p != '\0') {
1796 if (*p == '"' && p[-1] != '\\') {
1825 tail = (struct imap_status_item *) &items;
1832 item = g_malloc (sizeof (struct imap_status_item));
1834 item->name = g_strndup (name, p - name);
1835 item->value = strtoul (p, &p, 10);
1842 } while (*p != ')');
1849 static CamelFolder *
1850 get_folder_online (CamelStore *store, const char *folder_name, guint32 flags, CamelException *ex)
1852 CamelImapStore *imap_store = CAMEL_IMAP_STORE (store);
1853 CamelImapResponse *response;
1854 CamelFolder *new_folder;
1855 char *folder_dir, *storage_path;
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);
1862 camel_exception_clear(ex);
1864 CAMEL_SERVICE_REC_LOCK(imap_store, connect_lock);
1866 if (!camel_imap_store_connected(imap_store, ex)) {
1867 CAMEL_SERVICE_REC_UNLOCK (imap_store, connect_lock);
1871 if (!g_ascii_strcasecmp (folder_name, "INBOX"))
1872 folder_name = "INBOX";
1874 if (imap_store->current_folder) {
1875 camel_object_unref (imap_store->current_folder);
1876 imap_store->current_folder = NULL;
1878 response = camel_imap_command (imap_store, NULL, ex, "SELECT %F", folder_name);
1880 char *folder_real, *parent_name, *parent_real;
1883 if (camel_exception_get_id(ex) == CAMEL_EXCEPTION_USER_CANCEL) {
1884 CAMEL_SERVICE_REC_UNLOCK (imap_store, connect_lock);
1888 camel_exception_clear (ex);
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);
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))
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\""),
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);
1917 if (parent_real != NULL) {
1918 gboolean need_convert = FALSE;
1919 char *resp, *thisone;
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);
1930 /* FIXME: does not handle unexpected circumstances very well */
1931 for (i = 0; i < response->untagged->len; i++) {
1932 resp = response->untagged->pdata[i];
1934 if (!imap_parse_list_response (imap_store, resp, &flags, NULL, &thisone))
1937 if (!strcmp (parent_name, thisone)) {
1938 if (flags & CAMEL_FOLDER_NOINFERIORS)
1939 need_convert = TRUE;
1945 camel_imap_response_free (imap_store, response);
1947 /* if not, check if we can delete it and recreate it */
1949 struct imap_status_item *items, *item;
1950 guint32 messages = 0;
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;
1964 imap_status_item_free (items);
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);
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);
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",
1993 CAMEL_SERVICE_REC_UNLOCK (imap_store, connect_lock);
1994 g_free (parent_name);
1995 g_free (parent_real);
1998 camel_imap_response_free (imap_store, response);
2001 g_free (parent_real);
2004 g_free (parent_name);
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);
2009 camel_imap_store_summary_add_from_full(imap_store->summary, folder_real, imap_store->dir_sep);
2011 camel_imap_response_free (imap_store, response);
2013 response = camel_imap_command (imap_store, NULL, NULL, "SELECT %F", folder_name);
2015 g_free(folder_real);
2017 CAMEL_SERVICE_REC_UNLOCK (imap_store, connect_lock);
2020 } else if (flags & CAMEL_STORE_FOLDER_EXCL) {
2021 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
2022 _("Cannot create folder `%s': folder exists."),
2025 camel_imap_response_free_without_processing (imap_store, response);
2027 CAMEL_SERVICE_REC_UNLOCK (imap_store, connect_lock);
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);
2038 CamelException local_ex;
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);
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);
2053 camel_imap_response_free_without_processing (imap_store, response);
2055 CAMEL_SERVICE_REC_UNLOCK (imap_store, connect_lock);
2060 static CamelFolder *
2061 get_folder_offline (CamelStore *store, const char *folder_name,
2062 guint32 flags, CamelException *ex)
2064 CamelImapStore *imap_store = CAMEL_IMAP_STORE (store);
2065 CamelFolder *new_folder = NULL;
2068 if (!g_ascii_strcasecmp (folder_name, "INBOX"))
2069 folder_name = "INBOX";
2071 si = camel_store_summary_path((CamelStoreSummary *)imap_store->summary, folder_name);
2073 char *folder_dir, *storage_path;
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);
2081 camel_store_summary_info_free((CamelStoreSummary *)imap_store->summary, si);
2083 camel_exception_setv (ex, CAMEL_EXCEPTION_STORE_NO_FOLDER,
2084 _("No such folder %s"), folder_name);
2091 delete_folder (CamelStore *store, const char *folder_name, CamelException *ex)
2093 CamelImapStore *imap_store = CAMEL_IMAP_STORE (store);
2094 CamelImapResponse *response;
2096 CAMEL_SERVICE_REC_LOCK (imap_store, connect_lock);
2098 if (!camel_imap_store_connected(imap_store, ex))
2101 /* make sure this folder isn't currently SELECTed */
2102 response = camel_imap_command (imap_store, NULL, ex, "SELECT INBOX");
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;
2112 response = camel_imap_command(imap_store, NULL, ex, "DELETE %F", folder_name);
2114 camel_imap_response_free (imap_store, response);
2115 imap_forget_folder (imap_store, folder_name, ex);
2118 CAMEL_SERVICE_REC_UNLOCK(imap_store, connect_lock);
2122 manage_subscriptions (CamelStore *store, const char *old_name, gboolean subscribe)
2124 CamelImapStore *imap_store = CAMEL_IMAP_STORE (store);
2126 int olen = strlen(old_name);
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);
2134 path = camel_store_info_path(imap_store->summary, si);
2135 if (strncmp(path, old_name, olen) == 0) {
2137 subscribe_folder(store, path, NULL);
2139 unsubscribe_folder(store, path, NULL);
2141 camel_store_summary_info_free((CamelStoreSummary *)imap_store->summary, si);
2147 rename_folder_info (CamelImapStore *imap_store, const char *old_name, const char *new_name)
2151 int olen = strlen(old_name);
2153 char *npath, *nfull;
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);
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);
2165 npath = g_strdup(new_name);
2166 nfull = camel_imap_store_summary_path_to_full(imap_store->summary, npath, imap_store->dir_sep);
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;
2173 response = camel_imap_command (imap_store, NULL, NULL, "RENAME %F %G", path, nfull);
2175 camel_imap_response_free (imap_store, response);
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);
2181 camel_store_summary_touch((CamelStoreSummary *)imap_store->summary);
2185 camel_store_summary_info_free((CamelStoreSummary *)imap_store->summary, si);
2190 rename_folder (CamelStore *store, const char *old_name, const char *new_name_in, CamelException *ex)
2192 CamelImapStore *imap_store = CAMEL_IMAP_STORE (store);
2193 CamelImapResponse *response;
2194 char *oldpath, *newpath, *storage_path;
2196 CAMEL_SERVICE_REC_LOCK (imap_store, connect_lock);
2198 if (!camel_imap_store_connected(imap_store, ex))
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");
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;
2214 imap_store->renaming = TRUE;
2215 if (imap_store->parameters & IMAP_PARAM_SUBSCRIPTIONS)
2216 manage_subscriptions(store, old_name, FALSE);
2218 response = camel_imap_command (imap_store, NULL, ex, "RENAME %F %F", old_name, new_name_in);
2220 if (imap_store->parameters & IMAP_PARAM_SUBSCRIPTIONS)
2221 manage_subscriptions(store, old_name, TRUE);
2225 camel_imap_response_free (imap_store, response);
2227 /* rename summary, and handle broken server */
2228 rename_folder_info(imap_store, old_name, new_name_in);
2230 if (imap_store->parameters & IMAP_PARAM_SUBSCRIPTIONS)
2231 manage_subscriptions(store, new_name_in, TRUE);
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);
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));
2247 imap_store->renaming = FALSE;
2248 CAMEL_SERVICE_REC_UNLOCK(imap_store, connect_lock);
2251 static CamelFolderInfo *
2252 create_folder (CamelStore *store, const char *parent_name,
2253 const char *folder_name, CamelException *ex)
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;
2264 if (!camel_disco_store_check_online (CAMEL_DISCO_STORE (store), ex))
2270 while (*c && *c != imap_store->dir_sep && !strchr ("#%*", *c))
2274 camel_exception_setv (ex, CAMEL_EXCEPTION_FOLDER_INVALID_PATH,
2275 _("The folder name \"%s\" is invalid because it contains the character \"%c\""),
2280 /* check if the parent allows inferiors */
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);
2290 need_convert = FALSE;
2291 response = camel_imap_command (imap_store, NULL, ex, "LIST \"\" %G",
2293 if (!response) /* whoa, this is bad */ {
2294 g_free(parent_real);
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];
2302 if (!imap_parse_list_response (imap_store, resp, &flags, NULL, &thisone))
2305 if (strcmp (thisone, parent_name) == 0) {
2306 if (flags & CAMEL_FOLDER_NOINFERIORS)
2307 need_convert = TRUE;
2313 camel_imap_response_free (imap_store, response);
2315 camel_exception_init (&internal_ex);
2317 /* if not, check if we can delete it and recreate it */
2319 struct imap_status_item *items, *item;
2320 guint32 messages = 0;
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;
2333 imap_status_item_free (items);
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);
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);
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",
2356 g_free(parent_real);
2359 camel_imap_response_free (imap_store, response);
2361 root = imap_build_folder_info(imap_store, parent_name);
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);
2368 response = camel_imap_command (imap_store, NULL, ex, "CREATE %G", full_name);
2371 CamelImapStoreInfo *si;
2372 CamelFolderInfo *fi;
2374 camel_imap_response_free (imap_store, response);
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;
2386 camel_object_trigger_event (CAMEL_OBJECT (store), "folder_created", 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);
2395 g_free(parent_real);
2400 static CamelFolderInfo *
2401 parse_list_response_as_folder_info (CamelImapStore *imap_store,
2402 const char *response)
2404 CamelFolderInfo *fi;
2406 char sep, *dir, *path;
2408 CamelImapStoreInfo *si;
2411 if (!imap_parse_list_response (imap_store, response, &flags, &sep, &dir))
2414 /* FIXME: should use imap_build_folder_info, note the differences with param setting tho */
2416 si = camel_imap_store_summary_add_from_full(imap_store->summary, dir, sep?sep:'/');
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);
2427 flags = (flags & ~CAMEL_FOLDER_SUBSCRIBED) | (si->info.flags & CAMEL_STORE_FOLDER_INFO_SUBSCRIBED);
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"));
2435 fi->name = g_strdup(camel_store_info_name(imap_store->summary, si));
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;
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);
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);
2460 static int imap_match_pattern(char dir_sep, const char *pattern, const char *name)
2470 } else if (p == '%') {
2476 } else if (p == '*') {
2482 return n == 0 && (p == '%' || p == 0);
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)
2491 if (g_ascii_strcasecmp(a, "INBOX") == 0)
2494 return g_str_hash(a);
2497 static int folder_eq(const void *ap, const void *bp)
2502 if (g_ascii_strcasecmp(a, "INBOX") == 0)
2504 if (g_ascii_strcasecmp(b, "INBOX") == 0)
2507 return g_str_equal(a, b);
2511 get_folders_free(void *k, void *v, void *d)
2513 camel_folder_info_free(v);
2517 get_folders_sync(CamelImapStore *imap_store, const char *pattern, CamelException *ex)
2519 CamelImapResponse *response;
2520 CamelFolderInfo *fi, *hfi;
2523 GHashTable *present;
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);
2530 response = camel_imap_command (imap_store, NULL, ex,
2531 "%s \"\" %G", j==1 ? "LSUB" : "LIST",
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);
2540 hfi = g_hash_table_lookup(present, fi->full_name);
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;
2547 g_hash_table_insert(present, fi->full_name, fi);
2550 hfi->flags |= CAMEL_STORE_INFO_FOLDER_SUBSCRIBED;
2551 camel_folder_info_free(fi);
2555 camel_imap_response_free (imap_store, response);
2558 /* Sync summary to match */
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);
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);
2574 camel_store_summary_remove((CamelStoreSummary *)imap_store->summary, si);
2579 camel_store_summary_info_free((CamelStoreSummary *)imap_store->summary, si);
2582 g_hash_table_foreach(present, get_folders_free, NULL);
2583 g_hash_table_destroy(present);
2588 dumpfi(CamelFolderInfo *fi)
2591 CamelFolderInfo *n = fi;
2603 printf("%-40s %-30s %*s\n", fi->path, fi->full_name, depth*2+strlen(fi->url), fi->url);
2612 fill_fi(CamelStore *store, CamelFolderInfo *fi, guint32 flags)
2614 CamelFolder *folder;
2616 folder = camel_object_bag_peek(store->folders, fi->full_name);
2618 fi->unread = camel_folder_get_unread_message_count(folder);
2619 fi->total = camel_folder_get_message_count(folder);
2620 camel_object_unref(folder);
2624 struct _refresh_msg {
2625 CamelSessionThreadMsg msg;
2632 refresh_refresh(CamelSession *session, CamelSessionThreadMsg *msg)
2634 struct _refresh_msg *m = (struct _refresh_msg *)msg;
2635 CamelImapStore *store = (CamelImapStore *)m->store;
2637 CAMEL_SERVICE_REC_LOCK(m->store, connect_lock);
2639 if (!camel_imap_store_connected((CamelImapStore *)m->store, &m->ex))
2642 if (store->namespace && store->namespace[0]) {
2645 get_folders_sync(store, "INBOX", &m->ex);
2646 if (camel_exception_is_set(&m->ex))
2648 get_folders_sync(store, store->namespace, &m->ex);
2649 if (camel_exception_is_set(&m->ex))
2651 pattern = imap_concat(store, store->namespace, "*");
2652 get_folders_sync(store, pattern, &m->ex);
2655 get_folders_sync((CamelImapStore *)m->store, "*", &m->ex);
2657 camel_store_summary_save((CamelStoreSummary *)((CamelImapStore *)m->store)->summary);
2659 CAMEL_SERVICE_REC_UNLOCK(m->store, connect_lock);
2663 refresh_free(CamelSession *session, CamelSessionThreadMsg *msg)
2665 struct _refresh_msg *m = (struct _refresh_msg *)msg;
2667 camel_object_unref(m->store);
2668 camel_exception_clear(&m->ex);
2671 static CamelSessionThreadOps refresh_ops = {
2676 static CamelFolderInfo *
2677 get_folder_info_online (CamelStore *store, const char *top, guint32 flags, CamelException *ex)
2679 CamelImapStore *imap_store = CAMEL_IMAP_STORE (store);
2680 CamelFolderInfo *tree = NULL;
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 */
2689 if (camel_debug("imap:folder_info"))
2690 printf("get folder info online\n");
2692 if ((flags & CAMEL_STORE_FOLDER_INFO_SUBSCRIBED)
2693 && camel_store_summary_count((CamelStoreSummary *)imap_store->summary) > 0) {
2698 ref = now > imap_store->refresh_stamp+60*60*1;
2700 CAMEL_SERVICE_REC_LOCK(store, connect_lock);
2701 ref = now > imap_store->refresh_stamp+60*60*1;
2703 struct _refresh_msg *m;
2705 imap_store->refresh_stamp = now;
2707 m = camel_session_thread_msg_new(((CamelService *)store)->session, &refresh_ops, sizeof(*m));
2709 camel_object_ref(store);
2710 camel_exception_init(&m->ex);
2711 camel_session_thread_queue(((CamelService *)store)->session, &m->msg, 0);
2713 CAMEL_SERVICE_REC_UNLOCK(store, connect_lock);
2719 CAMEL_SERVICE_REC_LOCK(store, connect_lock);
2721 if (!camel_imap_store_connected((CamelImapStore *)store, ex))
2725 if (imap_store->namespace && imap_store->namespace[0]) {
2726 get_folders_sync(imap_store, "INBOX", ex);
2727 if (camel_exception_is_set(ex))
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)
2737 pattern = g_alloca(2);
2745 name = camel_imap_store_summary_full_from_path(imap_store->summary, top);
2747 name = camel_imap_store_summary_path_to_full(imap_store->summary, top, imap_store->dir_sep);
2750 pattern = g_alloca(i+5);
2751 strcpy(pattern, name);
2755 get_folders_sync(imap_store, pattern, ex);
2756 if (camel_exception_is_set(ex))
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)?'*':'%';
2762 get_folders_sync(imap_store, pattern, ex);
2764 camel_store_summary_save((CamelStoreSummary *)imap_store->summary);
2765 CAMEL_SERVICE_REC_UNLOCK(store, connect_lock);
2768 tree = get_folder_info_offline(store, top, flags, ex);
2772 CAMEL_SERVICE_REC_UNLOCK(store, connect_lock);
2776 static CamelFolderInfo *
2777 get_folder_info_offline (CamelStore *store, const char *top,
2778 guint32 flags, CamelException *ex)
2780 CamelImapStore *imap_store = CAMEL_IMAP_STORE (store);
2781 gboolean include_inbox = FALSE;
2782 CamelFolderInfo *fi;
2784 char *pattern, *name;
2787 if (camel_debug("imap:folder_info"))
2788 printf("get folder info offline\n");
2790 /* FIXME: obey other flags */
2792 folders = g_ptr_array_new ();
2794 if (top == NULL || top[0] == '\0') {
2795 include_inbox = TRUE;
2799 /* get starting point */
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;
2805 name = g_strdup("");
2807 name = camel_imap_store_summary_full_from_path(imap_store->summary, top);
2809 name = camel_imap_store_summary_path_to_full(imap_store->summary, top, imap_store->dir_sep);
2812 pattern = imap_concat(imap_store, name, "*");
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.
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);
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;
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;
2846 if (si->flags & CAMEL_FOLDER_NOSELECT) {
2847 CamelURL *url = camel_url_new(fi->uri, NULL);
2849 camel_url_set_param (url, "noselect", "yes");
2851 fi->uri = camel_url_to_string (url, 0);
2852 camel_url_free (url);
2854 fill_fi((CamelStore *)imap_store, fi, 0);
2857 fi->flags |= CAMEL_FOLDER_NOCHILDREN;
2858 g_ptr_array_add (folders, fi);
2860 camel_store_summary_info_free((CamelStoreSummary *)imap_store->summary, si);
2864 fi = camel_folder_info_build (folders, top, '/', TRUE);
2865 g_ptr_array_free (folders, TRUE);
2872 folder_subscribed (CamelStore *store, const char *folder_name)
2874 CamelImapStore *imap_store = CAMEL_IMAP_STORE (store);
2878 si = camel_store_summary_path((CamelStoreSummary *)imap_store->summary, folder_name);
2880 truth = (si->flags & CAMEL_STORE_INFO_FOLDER_SUBSCRIBED) != 0;
2881 camel_store_summary_info_free((CamelStoreSummary *)imap_store->summary, si);
2887 /* Note: folder_name must match a folder as listed with get_folder_info() -> full_name */
2889 subscribe_folder (CamelStore *store, const char *folder_name,
2892 CamelImapStore *imap_store = CAMEL_IMAP_STORE (store);
2893 CamelImapResponse *response;
2894 CamelFolderInfo *fi;
2897 CAMEL_SERVICE_REC_LOCK(store, connect_lock);
2899 if (!camel_imap_store_connected (imap_store, ex))
2902 response = camel_imap_command (imap_store, NULL, ex,
2903 "SUBSCRIBE %F", folder_name);
2906 camel_imap_response_free (imap_store, response);
2908 si = camel_store_summary_path((CamelStoreSummary *)imap_store->summary, folder_name);
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);
2915 camel_store_summary_info_free((CamelStoreSummary *)imap_store->summary, si);
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
2925 fi = imap_build_folder_info(imap_store, folder_name);
2926 fi->flags |= CAMEL_FOLDER_NOCHILDREN;
2928 camel_object_trigger_event (CAMEL_OBJECT (store), "folder_subscribed", fi);
2929 camel_folder_info_free (fi);
2931 CAMEL_SERVICE_REC_UNLOCK(store, connect_lock);
2935 unsubscribe_folder (CamelStore *store, const char *folder_name,
2938 CamelImapStore *imap_store = CAMEL_IMAP_STORE (store);
2939 CamelImapResponse *response;
2941 CAMEL_SERVICE_REC_LOCK(store, connect_lock);
2943 if (!camel_imap_store_connected (imap_store, ex))
2946 response = camel_imap_command (imap_store, NULL, ex,
2947 "UNSUBSCRIBE %F", folder_name);
2950 camel_imap_response_free (imap_store, response);
2952 imap_folder_effectively_unsubscribed (imap_store, folder_name, ex);
2954 CAMEL_SERVICE_REC_UNLOCK(store, connect_lock);
2959 folder_flags_have_changed (CamelFolder *folder)
2961 CamelMessageInfo *info;
2964 max = camel_folder_summary_count (folder->summary);
2965 for (i = 0; i < max; i++) {
2966 info = camel_folder_summary_index (folder->summary, i);
2969 if (info->flags & CAMEL_MESSAGE_FOLDER_FLAGGED) {
2978 /* Use this whenever you need to ensure you're both connected and
2981 camel_imap_store_connected (CamelImapStore *store, CamelException *ex)
2983 /* This looks stupid ... because it is.
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 ... */
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))
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"));
3003 /* FIXME: please god, when will the hurting stop? Thus function is so
3004 fucking broken it's not even funny. */
3006 camel_imap_store_readline (CamelImapStore *store, char **dest, CamelException *ex)
3008 CamelStreamBuffer *stream;
3009 char linebuf[1024] = {0};
3013 g_return_val_if_fail (CAMEL_IS_IMAP_STORE (store), -1);
3014 g_return_val_if_fail (dest, -1);
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.
3023 if (!camel_imap_store_connected (store, ex))
3026 stream = CAMEL_STREAM_BUFFER (store->istream);
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')
3037 camel_exception_set (ex, CAMEL_EXCEPTION_USER_CANCEL, _("Operation cancelled"));
3039 camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
3040 _("Server unexpectedly disconnected: %s"),
3041 g_strerror (errno));
3043 camel_service_disconnect (CAMEL_SERVICE (store), FALSE, NULL);
3044 g_byte_array_free (ba, TRUE);
3048 if (camel_verbose_debug) {
3049 fprintf (stderr, "received: ");
3050 fwrite (ba->data, 1, ba->len, stderr);
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';
3063 g_byte_array_free (ba, FALSE);