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 (C) 1999-2008 Novell, Inc. (www.novell.com)
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.
35 #include <glib/gi18n-lib.h>
36 #include <glib/gstdio.h>
38 #include "camel-imap-command.h"
39 #include "camel-imap-folder.h"
40 #include "camel-imap-message-cache.h"
41 #include "camel-imap-settings.h"
42 #include "camel-imap-store-summary.h"
43 #include "camel-imap-store.h"
44 #include "camel-imap-summary.h"
45 #include "camel-imap-utils.h"
54 /* Specified in RFC 2060 */
56 #define IMAPS_PORT 993
59 /* The strtok() in Microsoft's C library is MT-safe (but still uses
60 * only one buffer pointer per thread, but for the use of strtok_r()
61 * here that's enough).
63 #define strtok_r(s,sep,lasts) (*(lasts)=strtok((s),(sep)))
66 extern gint camel_verbose_debug;
68 static gchar imap_tag_prefix = 'A';
70 static gboolean imap_store_noop_sync (CamelStore *store, GCancellable *cancellable, GError **error);
71 static CamelFolder *imap_store_get_junk_folder_sync (CamelStore *store, GCancellable *cancellable, GError **error);
72 static CamelFolder *imap_store_get_trash_folder_sync (CamelStore *store, GCancellable *cancellable, GError **error);
73 static guint hash_folder_name (gconstpointer key);
74 static gboolean equal_folder_name (gconstpointer a, gconstpointer b);
76 static CamelFolderInfo *imap_store_create_folder_sync (CamelStore *store, const gchar *parent_name, const gchar *folder_name, GCancellable *cancellable, GError **error);
77 static gboolean imap_store_delete_folder_sync (CamelStore *store, const gchar *folder_name, GCancellable *cancellable, GError **error);
78 static gboolean imap_store_rename_folder_sync (CamelStore *store, const gchar *old_name, const gchar *new_name, GCancellable *cancellable, GError **error);
79 static gboolean imap_store_folder_is_subscribed (CamelSubscribable *subscribable, const gchar *folder_name);
80 static gboolean imap_store_subscribe_folder_sync (CamelSubscribable *subscribable, const gchar *folder_name, GCancellable *cancellable, GError **error);
81 static gboolean imap_store_unsubscribe_folder_sync (CamelSubscribable *subscribable, const gchar *folder_name, GCancellable *cancellable, GError **error);
83 static gboolean get_folders_sync (CamelImapStore *imap_store, const gchar *pattern, GCancellable *cancellable, GError **error);
85 static gboolean imap_folder_effectively_unsubscribed (CamelImapStore *imap_store, const gchar *folder_name, GError **error);
86 static gboolean imap_check_folder_still_extant (CamelImapStore *imap_store, const gchar *full_name, GError **error);
87 static void imap_forget_folder (CamelImapStore *imap_store, const gchar *folder_name, GError **error);
88 static void imap_set_server_level (CamelImapStore *store);
90 static gboolean imap_can_refresh_folder (CamelStore *store, CamelFolderInfo *info, GError **error);
91 static CamelFolder * imap_store_get_folder_sync (CamelStore *store, const gchar *folder_name, CamelStoreGetFolderFlags flags, GCancellable *cancellable, GError **error);
92 static CamelFolderInfo * imap_store_get_folder_info_sync (CamelStore *store, const gchar *top, CamelStoreGetFolderInfoFlags flags, GCancellable *cancellable, GError **error);
93 static CamelFolder * get_folder_offline (CamelStore *store, const gchar *folder_name, guint32 flags, GError **error);
94 static CamelFolderInfo * get_folder_info_offline (CamelStore *store, const gchar *top, guint32 flags, GError **error);
100 { "IMAP4", IMAP_CAPABILITY_IMAP4 },
101 { "IMAP4REV1", IMAP_CAPABILITY_IMAP4REV1 },
102 { "STATUS", IMAP_CAPABILITY_STATUS },
103 { "NAMESPACE", IMAP_CAPABILITY_NAMESPACE },
104 { "UIDPLUS", IMAP_CAPABILITY_UIDPLUS },
105 { "LITERAL+", IMAP_CAPABILITY_LITERALPLUS },
106 { "STARTTLS", IMAP_CAPABILITY_STARTTLS },
107 { "XGWEXTENSIONS", IMAP_CAPABILITY_XGWEXTENSIONS },
108 { "XGWMOVE", IMAP_CAPABILITY_XGWMOVE },
109 { "LOGINDISABLED", IMAP_CAPABILITY_LOGINDISABLED },
110 { "QUOTA", IMAP_CAPABILITY_QUOTA },
114 extern CamelServiceAuthType camel_imap_password_authtype;
116 static GInitableIface *parent_initable_interface;
118 /* Forward Declarations */
119 static void camel_imap_store_initable_init (GInitableIface *interface);
120 static void camel_network_service_init (CamelNetworkServiceInterface *interface);
121 static void camel_subscribable_init (CamelSubscribableInterface *interface);
123 G_DEFINE_TYPE_WITH_CODE (
126 CAMEL_TYPE_OFFLINE_STORE,
127 G_IMPLEMENT_INTERFACE (
129 camel_imap_store_initable_init)
130 G_IMPLEMENT_INTERFACE (
131 CAMEL_TYPE_NETWORK_SERVICE,
132 camel_network_service_init)
133 G_IMPLEMENT_INTERFACE (
134 CAMEL_TYPE_SUBSCRIBABLE,
135 camel_subscribable_init))
138 imap_store_update_store_flags (CamelStore *store)
140 CamelService *service;
141 CamelSettings *settings;
142 CamelImapSettings *imap_settings;
143 gboolean use_real_path;
146 /* XXX This only responds to the service's entire settings object
147 * being replaced, not when individual settings change. When
148 * individual settings change, a restart is required for them
151 service = CAMEL_SERVICE (store);
152 settings = camel_service_get_settings (service);
153 imap_settings = CAMEL_IMAP_SETTINGS (settings);
155 real_path = camel_imap_settings_dup_real_junk_path (imap_settings);
156 use_real_path = camel_imap_settings_get_use_real_junk_path (imap_settings);
158 if (use_real_path && real_path != NULL) {
159 store->flags &= ~CAMEL_STORE_VJUNK;
160 store->flags |= CAMEL_STORE_REAL_JUNK_FOLDER;
162 store->flags &= ~CAMEL_STORE_REAL_JUNK_FOLDER;
163 store->flags |= CAMEL_STORE_VJUNK;
168 real_path = camel_imap_settings_dup_real_trash_path (imap_settings);
169 use_real_path = camel_imap_settings_get_use_real_trash_path (imap_settings);
171 if (use_real_path && real_path != NULL)
172 store->flags &= ~CAMEL_STORE_VTRASH;
174 store->flags |= CAMEL_STORE_VTRASH;
180 parse_capability (CamelImapStore *store,
186 for (capa = strtok_r (capa, " ", &lasts); capa; capa = strtok_r (NULL, " ", &lasts)) {
187 if (!strncmp (capa, "AUTH=", 5)) {
188 g_hash_table_insert (store->authtypes,
190 GINT_TO_POINTER (1));
193 for (i = 0; capabilities[i].name; i++) {
194 if (g_ascii_strcasecmp (capa, capabilities[i].name) == 0) {
195 store->capabilities |= capabilities[i].flag;
202 static gboolean free_key (gpointer key, gpointer value, gpointer user_data);
205 imap_get_capability (CamelService *service,
206 GCancellable *cancellable,
209 CamelImapStore *store = CAMEL_IMAP_STORE (service);
210 CamelImapResponse *response;
213 /* Find out the IMAP capabilities */
214 /* We assume we have utf8 capable search until a failed search tells us otherwise */
215 store->capabilities = IMAP_CAPABILITY_utf8_search;
216 if (store->authtypes) {
217 g_hash_table_foreach_remove (store->authtypes, free_key, NULL);
218 g_hash_table_destroy (store->authtypes);
220 store->authtypes = g_hash_table_new (g_str_hash, g_str_equal);
221 response = camel_imap_command (store, NULL, cancellable, error, "CAPABILITY");
224 result = camel_imap_response_extract (store, response, "CAPABILITY ", error);
228 /* Skip over "* CAPABILITY ". */
229 parse_capability (store, result + 13);
232 /* dunno why the groupwise guys didn't just list this in capabilities */
233 if (store->capabilities & IMAP_CAPABILITY_XGWEXTENSIONS) {
234 /* not critical if this fails */
235 response = camel_imap_command (store, NULL, cancellable, NULL, "XGWEXTENSIONS");
236 if (response && (result = camel_imap_response_extract (store, response, "XGWEXTENSIONS ", NULL))) {
237 parse_capability (store, result + 16);
242 imap_set_server_level (store);
244 if (store->summary->capabilities != store->capabilities) {
245 store->summary->capabilities = store->capabilities;
246 camel_store_summary_touch ((CamelStoreSummary *) store->summary);
247 camel_store_summary_save ((CamelStoreSummary *) store->summary);
253 /* folder_name is path name */
254 static CamelFolderInfo *
255 imap_build_folder_info (CamelImapStore *imap_store,
256 const gchar *folder_name)
261 fi = camel_folder_info_new ();
262 fi->full_name = g_strdup (folder_name);
266 name = strrchr (fi->full_name, '/');
268 name = fi->full_name;
271 if (!g_ascii_strcasecmp (fi->full_name, "INBOX"))
272 fi->display_name = g_strdup (_("Inbox"));
274 fi->display_name = g_strdup (name);
280 connect_to_server (CamelService *service,
281 GCancellable *cancellable,
284 CamelImapStore *store = (CamelImapStore *) service;
285 CamelNetworkSettings *network_settings;
286 CamelSettings *settings;
287 CamelImapResponse *response;
288 CamelStream *tcp_stream;
289 CamelSockOptData sockopt;
290 CamelNetworkSecurityMethod method;
291 gboolean force_imap4 = FALSE;
292 gboolean clean_quit = TRUE;
293 gboolean success = TRUE;
297 tcp_stream = camel_network_service_connect_sync (
298 CAMEL_NETWORK_SERVICE (service), cancellable, error);
300 if (tcp_stream == NULL)
303 settings = camel_service_get_settings (service);
305 network_settings = CAMEL_NETWORK_SETTINGS (settings);
306 host = camel_network_settings_dup_host (network_settings);
307 method = camel_network_settings_get_security_method (network_settings);
309 store->ostream = tcp_stream;
310 store->istream = camel_stream_buffer_new (tcp_stream, CAMEL_STREAM_BUFFER_READ);
312 store->connected = TRUE;
313 store->preauthed = FALSE;
316 /* Disable Nagle - we send a lot of small requests which nagle slows down */
317 sockopt.option = CAMEL_SOCKOPT_NODELAY;
318 sockopt.value.no_delay = TRUE;
319 camel_tcp_stream_setsockopt ((CamelTcpStream *) tcp_stream, &sockopt);
321 /* Set keepalive - needed for some hosts/router configurations, we're idle a lot */
322 sockopt.option = CAMEL_SOCKOPT_KEEPALIVE;
323 sockopt.value.keep_alive = TRUE;
324 camel_tcp_stream_setsockopt ((CamelTcpStream *) tcp_stream, &sockopt);
326 /* Read the greeting, if any, and deal with PREAUTH */
327 if (camel_imap_store_readline (store, &buf, cancellable, error) < 0) {
328 if (store->istream) {
329 g_object_unref (store->istream);
330 store->istream = NULL;
333 if (store->ostream) {
334 g_object_unref (store->ostream);
335 store->ostream = NULL;
338 store->connected = FALSE;
344 if (!strncmp(buf, "* PREAUTH", 9))
345 store->preauthed = TRUE;
347 if (strstr (buf, "Courier-IMAP") || getenv("CAMEL_IMAP_BRAINDAMAGED")) {
348 /* Courier-IMAP is braindamaged. So far this flag only
349 * works around the fact that Courier-IMAP is known to
350 * give invalid BODY responses seemingly because its
351 * MIME parser sucks. In any event, we can't rely on
352 * them so we always have to request the full messages
353 * rather than getting individual parts. */
354 store->braindamaged = TRUE;
355 } else if (strstr (buf, "WEB.DE") || strstr (buf, "Mail2World")) {
356 /* This is a workaround for servers which advertise
357 * IMAP4rev1 but which can sometimes subtly break in
358 * various ways if we try to use IMAP4rev1 queries.
360 * WEB.DE: when querying for HEADER.FIELDS.NOT, it
361 * returns an empty literal for the headers. Many
362 * complaints about empty message-list fields on the
363 * mailing lists and probably a few bugzilla bugs as
366 * Mail2World (aka NamePlanet): When requesting
367 * message info's, it ignores the fact that we
368 * requested BODY.PEEK[HEADER.FIELDS.NOT (RECEIVED)]
369 * and so the responses are incomplete. See bug #58766
377 /* get the imap server capabilities */
378 if (!imap_get_capability (service, cancellable, error)) {
379 if (store->istream) {
380 g_object_unref (store->istream);
381 store->istream = NULL;
384 if (store->ostream) {
385 g_object_unref (store->ostream);
386 store->ostream = NULL;
389 store->connected = FALSE;
396 store->capabilities &= ~IMAP_CAPABILITY_IMAP4REV1;
397 store->server_level = IMAP_LEVEL_IMAP4;
400 if (method != CAMEL_NETWORK_SECURITY_METHOD_STARTTLS_ON_STANDARD_PORT)
401 goto exit; /* we're done */
403 /* as soon as we send a STARTTLS command, all hope is lost of a clean QUIT if problems arise */
406 if (!(store->capabilities & IMAP_CAPABILITY_STARTTLS)) {
408 error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
409 _("Failed to connect to IMAP server %s in secure mode: %s"),
410 host, _("STARTTLS not supported"));
415 response = camel_imap_command (store, NULL, cancellable, error, "STARTTLS");
417 g_object_unref (store->istream);
418 store->istream = NULL;
420 g_object_unref (store->ostream);
421 store->ostream = NULL;
428 camel_imap_response_free_without_processing (store, response);
430 /* Okay, now toggle SSL/TLS mode */
431 if (camel_tcp_stream_ssl_enable_ssl (
432 CAMEL_TCP_STREAM_SSL (tcp_stream), cancellable, error) == -1) {
435 _("Failed to connect to IMAP server %s in secure mode: "),
440 /* rfc2595, section 4 states that after a successful STLS
441 * command, the client MUST discard prior CAPA responses */
442 if (!imap_get_capability (service, cancellable, error)) {
443 if (store->istream) {
444 g_object_unref (store->istream);
445 store->istream = NULL;
448 if (store->ostream) {
449 g_object_unref (store->ostream);
450 store->ostream = NULL;
453 store->connected = FALSE;
459 if (store->capabilities & IMAP_CAPABILITY_LOGINDISABLED ) {
462 error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
463 _("Failed to connect to IMAP server %s in secure mode: %s"),
464 host, _("Unknown error"));
472 if (clean_quit && store->connected) {
473 /* try to disconnect cleanly */
474 response = camel_imap_command (store, NULL, cancellable, error, "LOGOUT");
476 camel_imap_response_free_without_processing (store, response);
479 if (store->istream) {
480 g_object_unref (store->istream);
481 store->istream = NULL;
484 if (store->ostream) {
485 g_object_unref (store->ostream);
486 store->ostream = NULL;
489 store->connected = FALSE;
501 /* Using custom commands to connect to IMAP servers is not supported on Win32 */
504 connect_to_server_process (CamelService *service,
506 GCancellable *cancellable,
509 CamelImapStore *store = (CamelImapStore *) service;
510 CamelNetworkSettings *network_settings;
511 CamelProvider *provider;
512 CamelSettings *settings;
513 CamelStream *cmd_stream;
520 const gchar *password;
525 memset (&url, 0, sizeof (CamelURL));
527 password = camel_service_get_password (service);
528 provider = camel_service_get_provider (service);
529 settings = camel_service_get_settings (service);
531 network_settings = CAMEL_NETWORK_SETTINGS (settings);
532 host = camel_network_settings_dup_host (network_settings);
533 port = camel_network_settings_get_port (network_settings);
534 user = camel_network_settings_dup_user (network_settings);
536 camel_url_set_protocol (&url, provider->protocol);
537 camel_url_set_host (&url, host);
538 camel_url_set_port (&url, port);
539 camel_url_set_user (&url, user);
541 /* Put full details in the environment, in case the connection
542 * program needs them */
543 buf = camel_url_to_string (&url, 0);
544 child_env[i++] = g_strdup_printf("URL=%s", buf);
547 child_env[i++] = g_strdup_printf("URLHOST=%s", host);
549 child_env[i++] = g_strdup_printf("URLPORT=%d", port);
551 child_env[i++] = g_strdup_printf("URLUSER=%s", user);
553 child_env[i++] = g_strdup_printf("URLPASSWD=%s", password);
556 /* Now do %h, %u, etc. substitution in cmd */
557 buf = cmd_copy = g_strdup (cmd);
559 full_cmd = g_strdup("");
567 pc = strchr (buf, '%');
570 tmp = g_strdup_printf("%s%s", full_cmd, buf);
589 /* If there wasn't a valid %-code, with an actual
590 * variable to insert, pretend we didn't see the % */
591 pc = strchr (pc + 1, '%');
594 tmp = g_strdup_printf("%s%.*s%s", full_cmd, len, buf, var);
605 cmd_stream = camel_stream_process_new ();
607 ret = camel_stream_process_connect (
608 CAMEL_STREAM_PROCESS (cmd_stream),
609 full_cmd, (const gchar **) child_env, error);
612 g_free (child_env[--i]);
615 g_object_unref (cmd_stream);
622 store->ostream = cmd_stream;
623 store->istream = camel_stream_buffer_new (cmd_stream, CAMEL_STREAM_BUFFER_READ);
625 store->connected = TRUE;
626 store->preauthed = FALSE;
629 /* Read the greeting, if any, and deal with PREAUTH */
630 if (camel_imap_store_readline (store, &buf, cancellable, error) < 0) {
631 if (store->istream) {
632 g_object_unref (store->istream);
633 store->istream = NULL;
636 if (store->ostream) {
637 g_object_unref (store->ostream);
638 store->ostream = NULL;
641 store->connected = FALSE;
645 if (!strncmp(buf, "* PREAUTH", 9))
646 store->preauthed = TRUE;
649 /* get the imap server capabilities */
650 if (!imap_get_capability (service, cancellable, error)) {
651 if (store->istream) {
652 g_object_unref (store->istream);
653 store->istream = NULL;
656 if (store->ostream) {
657 g_object_unref (store->ostream);
658 store->ostream = NULL;
661 store->connected = FALSE;
672 connect_to_server_wrapper (CamelService *service,
673 GCancellable *cancellable,
676 CamelSettings *settings;
677 gchar *shell_command;
678 gboolean use_shell_command;
681 settings = camel_service_get_settings (service);
682 shell_command = camel_imap_settings_dup_shell_command (
683 CAMEL_IMAP_SETTINGS (settings));
684 use_shell_command = camel_imap_settings_get_use_shell_command (
685 CAMEL_IMAP_SETTINGS (settings));
688 if (use_shell_command && shell_command != NULL)
689 success = connect_to_server_process (
690 service, shell_command, cancellable, error);
692 success = connect_to_server (service, cancellable, error);
694 success = connect_to_server (service, cancellable, error);
697 g_free (shell_command);
703 imap_auth_loop (CamelService *service,
704 GCancellable *cancellable,
707 CamelImapStore *store = CAMEL_IMAP_STORE (service);
708 CamelNetworkSettings *network_settings;
709 CamelSettings *settings;
710 CamelSession *session;
713 gboolean success = TRUE;
715 session = camel_service_get_session (service);
716 settings = camel_service_get_settings (service);
718 network_settings = CAMEL_NETWORK_SETTINGS (settings);
719 host = camel_network_settings_dup_host (network_settings);
720 mechanism = camel_network_settings_dup_auth_mechanism (network_settings);
722 if (store->preauthed) {
723 if (camel_verbose_debug)
724 fprintf(stderr, "Server %s has preauthenticated us.\n",
729 if (mechanism != NULL) {
730 if (!g_hash_table_lookup (store->authtypes, mechanism)) {
732 error, CAMEL_SERVICE_ERROR,
733 CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
734 _("IMAP server %s does not support %s "
735 "authentication"), host, mechanism);
741 success = camel_session_authenticate_sync (
742 session, service, mechanism, cancellable, error);
752 free_key (gpointer key,
761 imap_store_dispose (GObject *object)
763 CamelImapStore *imap_store = CAMEL_IMAP_STORE (object);
765 if (imap_store->summary != NULL) {
766 camel_store_summary_save (
767 CAMEL_STORE_SUMMARY (imap_store->summary));
768 g_object_unref (imap_store->summary);
769 imap_store->summary = NULL;
772 /* Chain up to parent's dispose() method. */
773 G_OBJECT_CLASS (camel_imap_store_parent_class)->dispose (object);
777 imap_store_finalize (GObject *object)
779 CamelImapStore *imap_store = CAMEL_IMAP_STORE (object);
781 /* This frees current_folder, folders, authtypes, streams, and namespace. */
782 camel_service_disconnect_sync (
783 CAMEL_SERVICE (imap_store), TRUE, NULL, NULL);
785 g_static_rec_mutex_free (&imap_store->command_and_response_lock);
786 g_hash_table_destroy (imap_store->known_alerts);
788 /* Chain up to parent's finalize() method. */
789 G_OBJECT_CLASS (camel_imap_store_parent_class)->finalize (object);
793 imap_store_get_name (CamelService *service,
796 CamelNetworkSettings *network_settings;
797 CamelSettings *settings;
802 settings = camel_service_get_settings (service);
804 network_settings = CAMEL_NETWORK_SETTINGS (settings);
805 host = camel_network_settings_dup_host (network_settings);
806 user = camel_network_settings_dup_user (network_settings);
809 name = g_strdup_printf (
810 _("IMAP server %s"), host);
812 name = g_strdup_printf (
813 _("IMAP service for %s on %s"), user, host);
822 imap_store_connect_sync (CamelService *service,
823 GCancellable *cancellable,
826 CamelImapStore *store = CAMEL_IMAP_STORE (service);
827 CamelImapResponse *response;
828 CamelSettings *settings;
829 CamelImapSettings *imap_settings;
830 gchar *result, *name;
832 const gchar *namespace;
833 GError *local_error = NULL;
835 settings = camel_service_get_settings (service);
836 imap_settings = CAMEL_IMAP_SETTINGS (settings);
838 if (!camel_offline_store_get_online (CAMEL_OFFLINE_STORE (store)))
841 if (!connect_to_server_wrapper (service, cancellable, error) ||
842 !imap_auth_loop (service, cancellable, error)) {
843 /* reset cancellable, in case it is cancelled,
844 * thus the disconnect is run */
846 g_cancellable_reset (cancellable);
847 camel_service_disconnect_sync (
848 service, TRUE, cancellable, NULL);
852 /* Get namespace and hierarchy separator */
853 if (store->capabilities & IMAP_CAPABILITY_NAMESPACE) {
854 struct _namespaces *namespaces;
855 const gchar *namespace;
857 response = camel_imap_command (store, NULL, cancellable, &local_error, "NAMESPACE");
861 result = camel_imap_response_extract (store, response, "NAMESPACE", &local_error);
865 namespaces = imap_parse_namespace_response (result);
867 if (!camel_imap_settings_get_use_namespace (imap_settings))
868 camel_imap_settings_set_namespace (imap_settings, NULL);
870 namespace = camel_imap_settings_get_namespace (imap_settings);
872 if (namespaces != NULL && namespace == NULL) {
873 struct _namespace *np = NULL;
875 if (namespaces->personal)
876 np = namespaces->personal;
877 else if (namespaces->other)
878 np = namespaces->other;
879 else if (namespaces->shared)
880 np = namespaces->shared;
883 camel_imap_settings_set_namespace (
884 imap_settings, np->prefix);
888 #define add_all(_ns) \
890 struct _namespace *ns; \
892 for (ns = _ns; ns; ns = ns->next) { \
894 camel_imap_store_summary_namespace_add_secondary \
895 (store->summary, ns->prefix, ns->delim); \
899 add_all (namespaces->personal);
900 add_all (namespaces->other);
901 add_all (namespaces->shared);
906 imap_namespaces_destroy (namespaces);
908 if (camel_imap_settings_get_namespace (imap_settings) == NULL) {
909 /* fallback for a broken result */
910 name = camel_strstrcase (result, "NAMESPACE ((");
916 ns = imap_parse_string (
917 (const gchar **) &name, &len);
918 camel_imap_settings_set_namespace (
922 if (name && *name++ == ' ') {
923 sep = imap_parse_string ((const gchar **) &name, &len);
925 store->dir_sep = *sep;
934 if (!store->dir_sep) {
935 const gchar *use_namespace = NULL;
937 if (store->summary->namespace != NULL)
938 use_namespace = store->summary->namespace->full_name;
940 if (use_namespace == NULL)
941 use_namespace = camel_imap_settings_get_namespace (
944 if (use_namespace == NULL)
947 if (store->server_level >= IMAP_LEVEL_IMAP4REV1) {
948 /* This idiom means "tell me the hierarchy separator
949 * for the given path, even if that path doesn't exist.
951 response = camel_imap_command (
952 store, NULL, cancellable, &local_error,
953 "LIST %G \"\"", use_namespace);
955 /* Plain IMAP4 doesn't have that idiom, so we fall back
956 * to "tell me about this folder", which will fail if
957 * the folder doesn't exist (eg, if namespace is "").
959 response = camel_imap_command (
960 store, NULL, cancellable, &local_error,
961 "LIST \"\" %G", use_namespace);
966 result = camel_imap_response_extract (store, response, "LIST", NULL);
968 imap_parse_list_response (store, result, NULL, &store->dir_sep, NULL);
973 store->dir_sep = '/'; /* Guess */
977 /* canonicalize the namespace to not end with dir_sep */
978 namespace = camel_imap_settings_get_namespace (imap_settings);
979 len = (namespace != NULL) ? strlen (namespace) : 0;
980 if (len && namespace[len - 1] == store->dir_sep) {
981 gchar *tmp = g_strdup (namespace);
983 camel_imap_settings_set_namespace (imap_settings, tmp);
984 namespace = camel_imap_settings_get_namespace (imap_settings);
988 camel_imap_store_summary_namespace_set_main (
989 store->summary, namespace, store->dir_sep);
991 if (camel_imap_settings_get_use_subscriptions (imap_settings) &&
992 camel_store_summary_count ((CamelStoreSummary *) store->summary) == 0) {
995 /* look in all namespaces */
996 if (!get_folders_sync (store, NULL, cancellable, &local_error))
999 /* Make sure INBOX is present/subscribed */
1000 si = camel_store_summary_path((CamelStoreSummary *)store->summary, "INBOX");
1001 if (si == NULL || (si->flags & CAMEL_FOLDER_SUBSCRIBED) == 0) {
1002 response = camel_imap_command (store, NULL, cancellable, &local_error, "SUBSCRIBE INBOX");
1003 if (response != NULL) {
1004 camel_imap_response_free (store, response);
1007 camel_store_summary_info_free ((CamelStoreSummary *) store->summary, si);
1008 if (local_error != NULL)
1010 get_folders_sync(store, "INBOX", cancellable, &local_error);
1013 store->refresh_stamp = time (NULL);
1017 /* save any changes we had */
1018 camel_store_summary_save ((CamelStoreSummary *) store->summary);
1020 if (local_error != NULL) {
1021 /* reset cancellable, in case it is cancelled,
1022 * thus the disconnect is run */
1024 g_cancellable_reset (cancellable);
1025 camel_service_disconnect_sync (
1026 service, TRUE, cancellable, NULL);
1027 g_propagate_error (error, local_error);
1035 imap_store_disconnect_sync (CamelService *service,
1037 GCancellable *cancellable,
1040 CamelImapStore *store = CAMEL_IMAP_STORE (service);
1041 CamelSettings *settings;
1042 CamelImapSettings *imap_settings;
1044 settings = camel_service_get_settings (service);
1045 imap_settings = CAMEL_IMAP_SETTINGS (settings);
1047 if (camel_offline_store_get_online (CAMEL_OFFLINE_STORE (store)) && clean) {
1048 CamelImapResponse *response;
1050 response = camel_imap_command (store, NULL, NULL, NULL, "LOGOUT");
1051 camel_imap_response_free (store, response);
1054 if (store->istream) {
1055 camel_stream_close (store->istream, cancellable, NULL);
1056 g_object_unref (store->istream);
1057 store->istream = NULL;
1060 if (store->ostream) {
1061 camel_stream_close (store->ostream, cancellable, NULL);
1062 g_object_unref (store->ostream);
1063 store->ostream = NULL;
1066 store->connected = FALSE;
1067 if (store->current_folder) {
1068 g_object_unref (store->current_folder);
1069 store->current_folder = NULL;
1072 if (store->authtypes) {
1073 g_hash_table_foreach_remove (store->authtypes,
1075 g_hash_table_destroy (store->authtypes);
1076 store->authtypes = NULL;
1079 g_hash_table_remove_all (store->known_alerts);
1081 if (camel_imap_settings_get_use_namespace (imap_settings))
1082 camel_imap_settings_set_namespace (imap_settings, NULL);
1087 static CamelAuthenticationResult
1088 imap_store_authenticate_sync (CamelService *service,
1089 const gchar *mechanism,
1090 GCancellable *cancellable,
1093 CamelImapStore *store = CAMEL_IMAP_STORE (service);
1094 CamelAuthenticationResult result;
1095 CamelImapResponse *response;
1096 CamelSasl *sasl = NULL;
1099 GError *local_error = NULL;
1101 /* If not using SASL, do a simple LOGIN here. */
1102 if (mechanism == NULL) {
1103 CamelNetworkSettings *network_settings;
1104 CamelSettings *settings;
1105 const gchar *password;
1108 password = camel_service_get_password (service);
1109 settings = camel_service_get_settings (service);
1111 network_settings = CAMEL_NETWORK_SETTINGS (settings);
1112 user = camel_network_settings_dup_user (network_settings);
1115 g_set_error_literal (
1116 error, CAMEL_SERVICE_ERROR,
1117 CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
1118 _("Cannot authenticate without a username"));
1119 return CAMEL_AUTHENTICATION_ERROR;
1122 if (password == NULL) {
1123 g_set_error_literal (
1124 error, CAMEL_SERVICE_ERROR,
1125 CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
1126 _("Authentication password not available"));
1128 return CAMEL_AUTHENTICATION_ERROR;
1131 response = camel_imap_command (
1132 store, NULL, cancellable, &local_error,
1133 "LOGIN %S %S", user, password);
1135 if (response != NULL)
1136 camel_imap_response_free (store, response);
1143 /* Henceforth we're using SASL. */
1145 sasl = camel_sasl_new ("imap", mechanism, service);
1148 error, CAMEL_SERVICE_ERROR,
1149 CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
1150 _("No support for %s authentication"), mechanism);
1151 return CAMEL_AUTHENTICATION_ERROR;
1154 response = camel_imap_command (
1155 store, NULL, cancellable, &local_error,
1156 "AUTHENTICATE %s", mechanism);
1157 if (response == NULL)
1160 while (!camel_sasl_get_authenticated (sasl)) {
1161 resp = camel_imap_response_extract_continuation (
1162 store, response, &local_error);
1167 sasl_resp = camel_sasl_challenge_base64_sync (
1168 sasl, imap_next_word (resp),
1169 cancellable, &local_error);
1173 if (sasl_resp == NULL)
1174 goto break_and_exit;
1176 response = camel_imap_command_continuation (
1177 store, NULL, sasl_resp, strlen (sasl_resp),
1178 cancellable, &local_error);
1182 if (response == NULL)
1186 resp = camel_imap_response_extract_continuation (store, response, NULL);
1188 /* Oops. SASL claims we're done, but the IMAP server
1189 * doesn't think so... */
1197 /* Get the server out of "waiting for continuation data" mode. */
1198 response = camel_imap_command_continuation (
1199 store, NULL, "*", 1, cancellable, NULL);
1200 if (response != NULL)
1201 camel_imap_response_free (store, response);
1204 /* XXX Apparently the IMAP parser sets CAMEL_SERVICE_ERROR_INVALID
1205 * for failed IMAP server responses, so I guess check for that
1206 * to know if our authentication credentials were rejected. */
1207 if (local_error == NULL) {
1208 result = CAMEL_AUTHENTICATION_ACCEPTED;
1209 } else if (g_error_matches (local_error, CAMEL_SERVICE_ERROR, CAMEL_SERVICE_ERROR_INVALID)) {
1210 g_clear_error (&local_error);
1211 result = CAMEL_AUTHENTICATION_REJECTED;
1213 /* Some servers (eg, courier) will disconnect
1214 * on a bad password, so we reconnect here. */
1215 if (!store->connected) {
1216 if (!connect_to_server_wrapper (
1217 service, cancellable, error))
1218 result = CAMEL_AUTHENTICATION_ERROR;
1222 g_propagate_error (error, local_error);
1223 result = CAMEL_AUTHENTICATION_ERROR;
1227 g_object_unref (sasl);
1233 imap_store_query_auth_types_sync (CamelService *service,
1234 GCancellable *cancellable,
1237 CamelImapStore *store = CAMEL_IMAP_STORE (service);
1238 CamelServiceAuthType *authtype;
1239 GList *sasl_types, *t, *next;
1242 if (!camel_offline_store_get_online (CAMEL_OFFLINE_STORE (store))) {
1244 error, CAMEL_SERVICE_ERROR,
1245 CAMEL_SERVICE_ERROR_UNAVAILABLE,
1246 _("You must be working online to complete this operation"));
1250 connected = store->istream != NULL && store->connected;
1252 connected = connect_to_server_wrapper (
1253 service, cancellable, error);
1257 sasl_types = camel_sasl_authtype_list (FALSE);
1258 for (t = sasl_types; t; t = next) {
1262 if (!g_hash_table_lookup (store->authtypes, authtype->authproto)) {
1263 sasl_types = g_list_remove_link (sasl_types, t);
1268 return g_list_prepend (sasl_types, &camel_imap_password_authtype);
1272 imap_migrate_to_user_cache_dir (CamelService *service)
1274 const gchar *user_data_dir, *user_cache_dir;
1276 g_return_if_fail (service != NULL);
1277 g_return_if_fail (CAMEL_IS_SERVICE (service));
1279 user_data_dir = camel_service_get_user_data_dir (service);
1280 user_cache_dir = camel_service_get_user_cache_dir (service);
1282 g_return_if_fail (user_data_dir != NULL);
1283 g_return_if_fail (user_cache_dir != NULL);
1285 /* migrate only if the source directory exists and the destination doesn't */
1286 if (g_file_test (user_data_dir, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR) &&
1287 !g_file_test (user_cache_dir, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR)) {
1290 parent_dir = g_path_get_dirname (user_cache_dir);
1291 g_mkdir_with_parents (parent_dir, S_IRWXU);
1292 g_free (parent_dir);
1294 if (g_rename (user_data_dir, user_cache_dir) == -1)
1295 g_debug ("%s: Failed to migrate '%s' to '%s': %s", G_STRFUNC, user_data_dir, user_cache_dir, g_strerror (errno));
1300 imap_store_initable_init (GInitable *initable,
1301 GCancellable *cancellable,
1304 CamelImapStore *imap_store;
1306 CamelService *service;
1307 const gchar *user_cache_dir;
1310 imap_store = CAMEL_IMAP_STORE (initable);
1311 store = CAMEL_STORE (initable);
1312 service = CAMEL_SERVICE (initable);
1314 store->flags |= CAMEL_STORE_USE_CACHE_DIR;
1315 imap_migrate_to_user_cache_dir (service);
1317 /* Chain up to parent interface's init() method. */
1318 if (!parent_initable_interface->init (initable, cancellable, error))
1321 service = CAMEL_SERVICE (initable);
1322 user_cache_dir = camel_service_get_user_cache_dir (service);
1324 /* setup/load the store summary */
1325 tmp_path = g_build_filename (user_cache_dir, ".ev-store-summary", NULL);
1326 imap_store->summary = camel_imap_store_summary_new ();
1327 camel_store_summary_set_filename ((CamelStoreSummary *) imap_store->summary, tmp_path);
1329 if (camel_store_summary_load ((CamelStoreSummary *) imap_store->summary) == 0) {
1330 CamelImapStoreSummary *is = imap_store->summary;
1332 /* XXX This won't work anymore. The CamelSettings
1333 * object for this store is not yet configured. */
1335 if (is->namespace) {
1336 const gchar *namespace;
1338 namespace = camel_imap_settings_get_namespace (
1339 CAMEL_IMAP_SETTINGS (settings));
1341 /* if namespace has changed, clear folder list */
1342 if (g_strcmp0 (namespace, is->namespace->full_name) != 0)
1343 camel_store_summary_clear ((CamelStoreSummary *) is);
1347 imap_store->capabilities = is->capabilities;
1348 imap_set_server_level (imap_store);
1354 static const gchar *
1355 imap_store_get_service_name (CamelNetworkService *service,
1356 CamelNetworkSecurityMethod method)
1358 const gchar *service_name;
1361 case CAMEL_NETWORK_SECURITY_METHOD_SSL_ON_ALTERNATE_PORT:
1362 service_name = "imaps";
1366 service_name = "imap";
1370 return service_name;
1374 imap_store_get_default_port (CamelNetworkService *service,
1375 CamelNetworkSecurityMethod method)
1377 guint16 default_port;
1380 case CAMEL_NETWORK_SECURITY_METHOD_SSL_ON_ALTERNATE_PORT:
1381 default_port = IMAPS_PORT;
1385 default_port = IMAP_PORT;
1389 return default_port;
1393 imap_store_folder_is_subscribed (CamelSubscribable *subscribable,
1394 const gchar *folder_name)
1396 CamelImapStore *imap_store = CAMEL_IMAP_STORE (subscribable);
1400 si = camel_store_summary_path ((CamelStoreSummary *) imap_store->summary, folder_name);
1402 truth = (si->flags & CAMEL_STORE_INFO_FOLDER_SUBSCRIBED) != 0;
1403 camel_store_summary_info_free ((CamelStoreSummary *) imap_store->summary, si);
1409 /* Note: folder_name must match a folder as listed with get_folder_info() -> full_name */
1411 imap_store_subscribe_folder_sync (CamelSubscribable *subscribable,
1412 const gchar *folder_name,
1413 GCancellable *cancellable,
1416 CamelImapStore *imap_store;
1417 CamelImapResponse *response;
1418 CamelFolderInfo *fi;
1421 imap_store = CAMEL_IMAP_STORE (subscribable);
1423 if (!camel_imap_store_connected (imap_store, error))
1426 response = camel_imap_command (imap_store, NULL, cancellable, error,
1427 "SUBSCRIBE %F", folder_name);
1431 camel_imap_response_free (imap_store, response);
1433 si = camel_store_summary_path ((CamelStoreSummary *) imap_store->summary, folder_name);
1435 if ((si->flags & CAMEL_STORE_INFO_FOLDER_SUBSCRIBED) == 0) {
1436 si->flags |= CAMEL_STORE_INFO_FOLDER_SUBSCRIBED;
1437 camel_store_summary_touch ((CamelStoreSummary *) imap_store->summary);
1438 camel_store_summary_save ((CamelStoreSummary *) imap_store->summary);
1440 camel_store_summary_info_free ((CamelStoreSummary *) imap_store->summary, si);
1443 if (imap_store->renaming) {
1444 /* we don't need to emit a "folder_subscribed" signal
1445 * if we are in the process of renaming folders, so we
1446 * are done here... */
1450 fi = imap_build_folder_info (imap_store, folder_name);
1451 fi->flags |= CAMEL_FOLDER_NOCHILDREN;
1453 camel_subscribable_folder_subscribed (subscribable, fi);
1454 camel_folder_info_free (fi);
1460 imap_store_unsubscribe_folder_sync (CamelSubscribable *subscribable,
1461 const gchar *folder_name,
1462 GCancellable *cancellable,
1465 CamelImapStore *imap_store;
1466 CamelImapResponse *response;
1468 imap_store = CAMEL_IMAP_STORE (subscribable);
1470 if (!camel_imap_store_connected (imap_store, error))
1473 response = camel_imap_command (imap_store, NULL, cancellable, error,
1474 "UNSUBSCRIBE %F", folder_name);
1478 camel_imap_response_free (imap_store, response);
1480 return imap_folder_effectively_unsubscribed (
1481 imap_store, folder_name, error);
1485 camel_imap_store_class_init (CamelImapStoreClass *class)
1487 GObjectClass *object_class;
1488 CamelServiceClass *service_class;
1489 CamelStoreClass *store_class;
1491 object_class = G_OBJECT_CLASS (class);
1492 object_class->dispose = imap_store_dispose;
1493 object_class->finalize = imap_store_finalize;
1495 service_class = CAMEL_SERVICE_CLASS (class);
1496 service_class->settings_type = CAMEL_TYPE_IMAP_SETTINGS;
1497 service_class->get_name = imap_store_get_name;
1498 service_class->connect_sync = imap_store_connect_sync;
1499 service_class->disconnect_sync = imap_store_disconnect_sync;
1500 service_class->authenticate_sync = imap_store_authenticate_sync;
1501 service_class->query_auth_types_sync = imap_store_query_auth_types_sync;
1503 store_class = CAMEL_STORE_CLASS (class);
1504 store_class->hash_folder_name = hash_folder_name;
1505 store_class->equal_folder_name = equal_folder_name;
1506 store_class->can_refresh_folder = imap_can_refresh_folder;
1507 store_class->free_folder_info = camel_store_free_folder_info_full;
1508 store_class->get_folder_sync = imap_store_get_folder_sync;
1509 store_class->get_folder_info_sync = imap_store_get_folder_info_sync;
1510 store_class->get_junk_folder_sync = imap_store_get_junk_folder_sync;
1511 store_class->get_trash_folder_sync = imap_store_get_trash_folder_sync;
1512 store_class->create_folder_sync = imap_store_create_folder_sync;
1513 store_class->delete_folder_sync = imap_store_delete_folder_sync;
1514 store_class->rename_folder_sync = imap_store_rename_folder_sync;
1515 store_class->noop_sync = imap_store_noop_sync;
1519 camel_imap_store_initable_init (GInitableIface *interface)
1521 parent_initable_interface = g_type_interface_peek_parent (interface);
1523 interface->init = imap_store_initable_init;
1527 camel_network_service_init (CamelNetworkServiceInterface *interface)
1529 interface->get_service_name = imap_store_get_service_name;
1530 interface->get_default_port = imap_store_get_default_port;
1534 camel_subscribable_init (CamelSubscribableInterface *interface)
1536 interface->folder_is_subscribed = imap_store_folder_is_subscribed;
1537 interface->subscribe_folder_sync = imap_store_subscribe_folder_sync;
1538 interface->unsubscribe_folder_sync = imap_store_unsubscribe_folder_sync;
1542 camel_imap_store_init (CamelImapStore *imap_store)
1544 g_static_rec_mutex_init (&imap_store->command_and_response_lock);
1546 imap_store->istream = NULL;
1547 imap_store->ostream = NULL;
1549 /* TODO: support dir_sep per namespace */
1550 imap_store->dir_sep = '\0';
1551 imap_store->current_folder = NULL;
1552 imap_store->connected = FALSE;
1553 imap_store->preauthed = FALSE;
1555 imap_store->tag_prefix = imap_tag_prefix++;
1556 if (imap_tag_prefix > 'Z')
1557 imap_tag_prefix = 'A';
1559 imap_store->known_alerts = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
1562 imap_store, "notify::settings",
1563 G_CALLBACK (imap_store_update_store_flags), NULL);
1567 imap_set_server_level (CamelImapStore *store)
1569 if (store->capabilities & IMAP_CAPABILITY_IMAP4REV1) {
1570 store->server_level = IMAP_LEVEL_IMAP4REV1;
1571 store->capabilities |= IMAP_CAPABILITY_STATUS;
1572 } else if (store->capabilities & IMAP_CAPABILITY_IMAP4)
1573 store->server_level = IMAP_LEVEL_IMAP4;
1575 store->server_level = IMAP_LEVEL_UNKNOWN;
1579 imap_folder_effectively_unsubscribed (CamelImapStore *imap_store,
1580 const gchar *folder_name,
1583 CamelFolderInfo *fi;
1586 si = camel_store_summary_path ((CamelStoreSummary *) imap_store->summary, folder_name);
1588 if (si->flags & CAMEL_STORE_INFO_FOLDER_SUBSCRIBED) {
1589 si->flags &= ~CAMEL_STORE_INFO_FOLDER_SUBSCRIBED;
1590 camel_store_summary_touch ((CamelStoreSummary *) imap_store->summary);
1591 camel_store_summary_save ((CamelStoreSummary *) imap_store->summary);
1593 camel_store_summary_info_free ((CamelStoreSummary *) imap_store->summary, si);
1596 if (imap_store->renaming) {
1597 /* we don't need to emit a "folder_unsubscribed" signal
1598 * if we are in the process of renaming folders, so we
1599 * are done here... */
1604 fi = imap_build_folder_info (imap_store, folder_name);
1605 camel_subscribable_folder_unsubscribed (
1606 CAMEL_SUBSCRIBABLE (imap_store), fi);
1607 camel_folder_info_free (fi);
1613 imap_forget_folder (CamelImapStore *imap_store,
1614 const gchar *folder_name,
1617 CamelService *service;
1618 const gchar *user_cache_dir;
1620 gchar *journal_file;
1621 gchar *folder_dir, *storage_path;
1622 CamelFolderInfo *fi;
1625 name = strrchr (folder_name, imap_store->dir_sep);
1631 service = CAMEL_SERVICE (imap_store);
1632 user_cache_dir = camel_service_get_user_cache_dir (service);
1634 storage_path = g_build_filename (user_cache_dir, "folders", NULL);
1635 folder_dir = imap_path_to_physical (storage_path, folder_name);
1636 g_free (storage_path);
1637 if (g_access (folder_dir, F_OK) != 0) {
1638 g_free (folder_dir);
1642 /* Delete summary and all the data */
1643 journal_file = g_strdup_printf ("%s/journal", folder_dir);
1644 g_unlink (journal_file);
1645 g_free (journal_file);
1647 state_file = g_strdup_printf ("%s/cmeta", folder_dir);
1648 g_unlink (state_file);
1649 g_free (state_file);
1651 camel_db_delete_folder (((CamelStore *) imap_store)->cdb_w, folder_name, NULL);
1652 camel_imap_message_cache_delete (folder_dir, NULL);
1654 state_file = g_strdup_printf("%s/subfolders", folder_dir);
1655 g_rmdir (state_file);
1656 g_free (state_file);
1658 g_rmdir (folder_dir);
1659 g_free (folder_dir);
1663 camel_store_summary_remove_path ((CamelStoreSummary *) imap_store->summary, folder_name);
1664 camel_store_summary_save ((CamelStoreSummary *) imap_store->summary);
1666 fi = imap_build_folder_info (imap_store, folder_name);
1667 camel_store_folder_deleted (CAMEL_STORE (imap_store), fi);
1668 camel_folder_info_free (fi);
1672 imap_check_folder_still_extant (CamelImapStore *imap_store,
1673 const gchar *full_name,
1676 CamelImapResponse *response;
1678 response = camel_imap_command (imap_store, NULL, NULL, NULL, "LIST \"\" %F",
1682 gboolean stillthere = response->untagged->len != 0;
1684 camel_imap_response_free_without_processing (imap_store, response);
1689 /* if the command was rejected, there must be some other error,
1690 * assume it worked so we dont blow away the folder unecessarily */
1695 imap_summary_is_dirty (CamelFolderSummary *summary)
1697 CamelImapMessageInfo *info;
1699 gboolean found = FALSE;
1700 GPtrArray *known_uids;
1702 known_uids = camel_folder_summary_get_array (summary);
1703 g_return_val_if_fail (known_uids != NULL, FALSE);
1705 for (i = 0; i < known_uids->len && !found; i++) {
1706 info = (CamelImapMessageInfo *) camel_folder_summary_get (summary, g_ptr_array_index (known_uids, i));
1708 found = info->info.flags & CAMEL_MESSAGE_FOLDER_FLAGGED;
1709 camel_message_info_free (info);
1713 camel_folder_summary_free_array (known_uids);
1719 imap_store_noop_sync (CamelStore *store,
1720 GCancellable *cancellable,
1723 CamelImapStore *imap_store = (CamelImapStore *) store;
1724 CamelImapResponse *response;
1725 CamelFolder *current_folder;
1726 gboolean success = TRUE;
1728 if (!camel_imap_store_connected (imap_store, error))
1731 current_folder = imap_store->current_folder;
1732 if (current_folder && imap_summary_is_dirty (current_folder->summary)) {
1733 /* let's sync the flags instead. NB: must avoid folder lock */
1734 success = CAMEL_FOLDER_GET_CLASS (current_folder)->synchronize_sync (
1735 current_folder, FALSE, cancellable, error);
1737 response = camel_imap_command (imap_store, NULL, cancellable, error, "NOOP");
1739 camel_imap_response_free (imap_store, response);
1747 static CamelFolder *
1748 imap_store_get_trash_folder_sync (CamelStore *store,
1749 GCancellable *cancellable,
1752 CamelService *service;
1753 CamelSettings *settings;
1754 CamelFolder *folder = NULL;
1755 const gchar *user_cache_dir;
1758 service = CAMEL_SERVICE (store);
1759 settings = camel_service_get_settings (service);
1760 user_cache_dir = camel_service_get_user_cache_dir (service);
1762 trash_path = camel_imap_settings_dup_real_trash_path (
1763 CAMEL_IMAP_SETTINGS (settings));
1764 if (trash_path != NULL) {
1765 folder = camel_store_get_folder_sync (
1766 store, trash_path, 0, cancellable, NULL);
1768 camel_imap_settings_set_real_trash_path (
1769 CAMEL_IMAP_SETTINGS (settings), NULL);
1771 g_free (trash_path);
1776 folder = CAMEL_STORE_CLASS (camel_imap_store_parent_class)->
1777 get_trash_folder_sync (store, cancellable, error);
1780 CamelObject *object = CAMEL_OBJECT (folder);
1783 state = g_build_filename (
1784 user_cache_dir, "system", "Trash.cmeta", NULL);
1786 camel_object_set_state_filename (object, state);
1789 camel_object_state_read (object);
1795 static CamelFolder *
1796 imap_store_get_junk_folder_sync (CamelStore *store,
1797 GCancellable *cancellable,
1800 CamelService *service;
1801 CamelSettings *settings;
1802 CamelFolder *folder = NULL;
1803 const gchar *user_cache_dir;
1806 service = CAMEL_SERVICE (store);
1807 settings = camel_service_get_settings (service);
1808 user_cache_dir = camel_service_get_user_cache_dir (service);
1810 junk_path = camel_imap_settings_dup_real_junk_path (
1811 CAMEL_IMAP_SETTINGS (settings));
1812 if (junk_path != NULL) {
1813 folder = camel_store_get_folder_sync (
1814 store, junk_path, 0, cancellable, NULL);
1816 camel_imap_settings_set_real_junk_path (
1817 CAMEL_IMAP_SETTINGS (settings), NULL);
1824 folder = CAMEL_STORE_CLASS (camel_imap_store_parent_class)->
1825 get_junk_folder_sync (store, cancellable, error);
1828 CamelObject *object = CAMEL_OBJECT (folder);
1831 state = g_build_filename (
1832 user_cache_dir, "system", "Junk.cmeta", NULL);
1834 camel_object_set_state_filename (object, state);
1837 camel_object_state_read (object);
1844 hash_folder_name (gconstpointer key)
1846 if (g_ascii_strcasecmp (key, "INBOX") == 0)
1847 return g_str_hash ("INBOX");
1849 return g_str_hash (key);
1853 equal_folder_name (gconstpointer a,
1856 gconstpointer aname = a, bname = b;
1858 if (g_ascii_strcasecmp (a, "INBOX") == 0)
1860 if (g_ascii_strcasecmp (b, "INBOX") == 0)
1862 return g_str_equal (aname, bname);
1865 struct imap_status_item {
1866 struct imap_status_item *next;
1872 imap_status_item_free (struct imap_status_item *items)
1874 struct imap_status_item *next;
1876 while (items != NULL) {
1878 g_free (items->name);
1884 static struct imap_status_item *
1885 get_folder_status (CamelImapStore *imap_store,
1886 const gchar *folder_name,
1889 struct imap_status_item *items, *item, *tail;
1890 CamelImapResponse *response;
1891 gchar *status, *name, *p;
1893 /* FIXME: we assume the server is STATUS-capable */
1895 response = camel_imap_command (imap_store, NULL, NULL, NULL,
1901 if (imap_check_folder_still_extant (imap_store, folder_name, NULL) == FALSE) {
1902 imap_folder_effectively_unsubscribed (imap_store, folder_name, NULL);
1903 imap_forget_folder (imap_store, folder_name, NULL);
1908 if (!(status = camel_imap_response_extract (imap_store, response, "STATUS", NULL)))
1911 p = status + strlen ("* STATUS ");
1915 /* skip past the mailbox string */
1918 while (*p != '\0') {
1919 if (*p == '"' && p[-1] != '\\') {
1948 tail = (struct imap_status_item *) &items;
1955 item = g_malloc (sizeof (struct imap_status_item));
1957 item->name = g_strndup (name, p - name);
1958 item->value = strtoul (p, &p, 10);
1965 } while (*p != ')');
1972 static CamelFolder *
1973 imap_store_get_folder_sync (CamelStore *store,
1974 const gchar *folder_name,
1975 CamelStoreGetFolderFlags flags,
1976 GCancellable *cancellable,
1979 CamelImapStore *imap_store = CAMEL_IMAP_STORE (store);
1980 CamelImapResponse *response;
1981 CamelFolder *new_folder;
1982 CamelService *service;
1983 const gchar *user_cache_dir;
1984 gchar *folder_dir, *storage_path;
1985 GError *local_error = NULL;
1987 service = CAMEL_SERVICE (store);
1988 user_cache_dir = camel_service_get_user_cache_dir (service);
1990 /* Try to get it locally first, if it is, then the client will
1991 * force a select when necessary */
1992 new_folder = get_folder_offline (store, folder_name, flags, &local_error);
1996 g_clear_error (&local_error);
1998 if (!camel_offline_store_get_online (CAMEL_OFFLINE_STORE (store))) {
2000 error, CAMEL_SERVICE_ERROR,
2001 CAMEL_SERVICE_ERROR_UNAVAILABLE,
2002 _("You must be working online to complete this operation"));
2006 if (!camel_imap_store_connected (imap_store, error))
2009 if (!g_ascii_strcasecmp (folder_name, "INBOX"))
2010 folder_name = "INBOX";
2012 if (imap_store->current_folder) {
2013 g_object_unref (imap_store->current_folder);
2014 imap_store->current_folder = NULL;
2017 response = camel_imap_command (imap_store, NULL, cancellable, &local_error, "SELECT %F", folder_name);
2019 gchar *folder_real, *parent_name, *parent_real;
2022 if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
2023 g_propagate_error (error, local_error);
2027 g_clear_error (&local_error);
2029 if (!(flags & CAMEL_STORE_FOLDER_CREATE)) {
2031 error, CAMEL_STORE_ERROR,
2032 CAMEL_STORE_ERROR_NO_FOLDER,
2033 _("No such folder %s"), folder_name);
2037 parent_name = strrchr (folder_name, '/');
2038 c = parent_name ? parent_name + 1 : folder_name;
2039 while (*c && *c != imap_store->dir_sep && !strchr ("#%*", *c))
2044 error, CAMEL_FOLDER_ERROR,
2045 CAMEL_FOLDER_ERROR_INVALID_PATH,
2046 _("The folder name \"%s\" is invalid because it contains the character \"%c\""),
2052 parent_name = g_strndup (folder_name, parent_name - folder_name);
2053 parent_real = camel_imap_store_summary_path_to_full (imap_store->summary, parent_name, imap_store->dir_sep);
2058 if (parent_real != NULL) {
2059 gboolean need_convert = FALSE;
2060 gchar *resp, *thisone;
2064 if (!(response = camel_imap_command (imap_store, NULL, cancellable, error, "LIST \"\" %G", parent_real))) {
2065 g_free (parent_name);
2066 g_free (parent_real);
2070 /* FIXME: does not handle unexpected circumstances very well */
2071 for (i = 0; i < response->untagged->len; i++) {
2072 resp = response->untagged->pdata[i];
2074 if (!imap_parse_list_response (imap_store, resp, &flags, NULL, &thisone))
2077 if (!strcmp (parent_name, thisone)) {
2078 if (flags & CAMEL_FOLDER_NOINFERIORS)
2079 need_convert = TRUE;
2085 camel_imap_response_free (imap_store, response);
2087 /* if not, check if we can delete it and recreate it */
2089 struct imap_status_item *items, *item;
2090 guint32 messages = 0;
2093 item = items = get_folder_status (imap_store, parent_name, "MESSAGES");
2094 while (item != NULL) {
2095 if (!g_ascii_strcasecmp (item->name, "MESSAGES")) {
2096 messages = item->value;
2103 imap_status_item_free (items);
2107 error, CAMEL_FOLDER_ERROR,
2108 CAMEL_FOLDER_ERROR_INVALID_STATE,
2109 _("The parent folder is not allowed to contain subfolders"));
2110 g_free (parent_name);
2111 g_free (parent_real);
2115 /* delete the old parent and recreate it */
2116 if (!imap_store_delete_folder_sync (
2117 store, parent_name, cancellable, error)) {
2118 g_free (parent_name);
2119 g_free (parent_real);
2123 /* add the dirsep to the end of parent_name */
2124 name = g_strdup_printf ("%s%c", parent_real, imap_store->dir_sep);
2125 response = camel_imap_command (imap_store, NULL, cancellable, error, "CREATE %G",
2130 g_free (parent_name);
2131 g_free (parent_real);
2134 camel_imap_response_free (imap_store, response);
2137 g_free (parent_real);
2140 g_free (parent_name);
2142 folder_real = camel_imap_store_summary_path_to_full (imap_store->summary, folder_name, imap_store->dir_sep);
2143 response = camel_imap_command (imap_store, NULL, cancellable, error, "CREATE %G", folder_real);
2145 camel_imap_store_summary_add_from_full (imap_store->summary, folder_real, imap_store->dir_sep);
2147 camel_imap_response_free (imap_store, response);
2149 response = camel_imap_command (imap_store, NULL, NULL, NULL, "SELECT %F", folder_name);
2151 g_free (folder_real);
2155 } else if (flags & CAMEL_STORE_FOLDER_EXCL) {
2157 error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
2158 _("Cannot create folder '%s': folder exists."),
2161 camel_imap_response_free_without_processing (imap_store, response);
2166 storage_path = g_build_filename (user_cache_dir, "folders", NULL);
2167 folder_dir = imap_path_to_physical (storage_path, folder_name);
2168 g_free (storage_path);
2169 new_folder = camel_imap_folder_new (store, folder_name, folder_dir, error);
2170 g_free (folder_dir);
2172 imap_store->current_folder = g_object_ref (new_folder);
2173 if (!camel_imap_folder_selected (
2174 new_folder, response, cancellable, error)) {
2176 g_object_unref (imap_store->current_folder);
2177 imap_store->current_folder = NULL;
2178 g_object_unref (new_folder);
2182 camel_imap_response_free_without_processing (imap_store, response);
2187 static CamelFolder *
2188 get_folder_offline (CamelStore *store,
2189 const gchar *folder_name,
2193 CamelImapStore *imap_store = CAMEL_IMAP_STORE (store);
2194 CamelFolder *new_folder = NULL;
2196 CamelService *service;
2197 const gchar *user_cache_dir;
2199 service = CAMEL_SERVICE (store);
2200 user_cache_dir = camel_service_get_user_cache_dir (service);
2202 si = camel_store_summary_path ((CamelStoreSummary *) imap_store->summary, folder_name);
2204 gchar *folder_dir, *storage_path;
2206 /* Note: Although the INBOX is defined to be case-insensitive in the IMAP RFC
2207 * it is still up to the server how to acutally name it in a LIST response. Since
2208 * we stored the name as the server provided it us in the summary we take that name
2209 * to look up the folder.
2210 * But for the on-disk cache we do always capitalize the Inbox no matter what the
2213 if (!g_ascii_strcasecmp (folder_name, "INBOX"))
2214 folder_name = "INBOX";
2216 storage_path = g_build_filename (user_cache_dir, "folders", NULL);
2217 folder_dir = imap_path_to_physical (storage_path, folder_name);
2218 g_free (storage_path);
2219 new_folder = camel_imap_folder_new (store, folder_name, folder_dir, error);
2220 g_free (folder_dir);
2222 camel_store_summary_info_free ((CamelStoreSummary *) imap_store->summary, si);
2225 error, CAMEL_STORE_ERROR,
2226 CAMEL_STORE_ERROR_NO_FOLDER,
2227 _("No such folder %s"), folder_name);
2234 imap_store_delete_folder_sync (CamelStore *store,
2235 const gchar *folder_name,
2236 GCancellable *cancellable,
2239 CamelImapStore *imap_store = CAMEL_IMAP_STORE (store);
2240 CamelImapResponse *response;
2241 gboolean success = TRUE;
2243 if (!camel_imap_store_connected (imap_store, error))
2246 /* make sure this folder isn't currently SELECTed */
2247 response = camel_imap_command (imap_store, NULL, cancellable, error, "SELECT INBOX");
2251 camel_imap_response_free_without_processing (imap_store, response);
2252 if (imap_store->current_folder)
2253 g_object_unref (imap_store->current_folder);
2254 /* no need to actually create a CamelFolder for INBOX */
2255 imap_store->current_folder = NULL;
2257 response = camel_imap_command(imap_store, NULL, cancellable, error, "DELETE %F", folder_name);
2259 camel_imap_response_free (imap_store, response);
2260 imap_forget_folder (imap_store, folder_name, NULL);
2268 manage_subscriptions (CamelStore *store,
2269 const gchar *old_name,
2271 GCancellable *cancellable)
2273 CamelImapStore *imap_store = CAMEL_IMAP_STORE (store);
2275 gint olen = strlen (old_name);
2279 count = camel_store_summary_count ((CamelStoreSummary *) imap_store->summary);
2280 for (i = 0; i < count; i++) {
2281 si = camel_store_summary_index ((CamelStoreSummary *) imap_store->summary, i);
2283 path = camel_store_info_path (imap_store->summary, si);
2284 if (strncmp (path, old_name, olen) == 0) {
2286 imap_store_subscribe_folder_sync (
2287 CAMEL_SUBSCRIBABLE (store),
2288 path, cancellable, NULL);
2290 imap_store_unsubscribe_folder_sync (
2291 CAMEL_SUBSCRIBABLE (store),
2292 path, cancellable, NULL);
2294 camel_store_summary_info_free ((CamelStoreSummary *) imap_store->summary, si);
2300 rename_folder_info (CamelImapStore *imap_store,
2301 const gchar *old_name,
2302 const gchar *new_name)
2306 gint olen = strlen (old_name);
2308 gchar *npath, *nfull;
2310 count = camel_store_summary_count ((CamelStoreSummary *) imap_store->summary);
2311 for (i = 0; i < count; i++) {
2312 si = camel_store_summary_index ((CamelStoreSummary *) imap_store->summary, i);
2315 path = camel_store_info_path (imap_store->summary, si);
2316 if (strncmp (path, old_name, olen) == 0) {
2317 if (strlen (path) > olen)
2318 npath = g_strdup_printf("%s/%s", new_name, path+olen+1);
2320 npath = g_strdup (new_name);
2321 nfull = camel_imap_store_summary_path_to_full (imap_store->summary, npath, imap_store->dir_sep);
2323 /* workaround for broken server (courier uses '.') that doesn't rename
2324 * subordinate folders as required by rfc 2060 */
2325 if (imap_store->dir_sep == '.') {
2326 CamelImapResponse *response;
2328 response = camel_imap_command (imap_store, NULL, NULL, NULL, "RENAME %F %G", path, nfull);
2330 camel_imap_response_free (imap_store, response);
2333 camel_store_info_set_string ((CamelStoreSummary *) imap_store->summary, si, CAMEL_STORE_INFO_PATH, npath);
2334 camel_store_info_set_string ((CamelStoreSummary *) imap_store->summary, si, CAMEL_IMAP_STORE_INFO_FULL_NAME, nfull);
2336 camel_store_summary_touch ((CamelStoreSummary *) imap_store->summary);
2340 camel_store_summary_info_free ((CamelStoreSummary *) imap_store->summary, si);
2345 imap_store_rename_folder_sync (CamelStore *store,
2346 const gchar *old_name,
2347 const gchar *new_name_in,
2348 GCancellable *cancellable,
2351 CamelImapStore *imap_store = CAMEL_IMAP_STORE (store);
2352 CamelImapResponse *response;
2353 CamelService *service;
2354 CamelSettings *settings;
2355 const gchar *user_cache_dir;
2356 gchar *oldpath, *newpath, *storage_path;
2357 gboolean use_subscriptions;
2358 gboolean success = TRUE;
2360 service = CAMEL_SERVICE (store);
2361 settings = camel_service_get_settings (service);
2362 user_cache_dir = camel_service_get_user_cache_dir (service);
2364 use_subscriptions = camel_imap_settings_get_use_subscriptions (
2365 CAMEL_IMAP_SETTINGS (settings));
2367 if (!camel_imap_store_connected (imap_store, error)) {
2372 /* make sure this folder isn't currently SELECTed - it's
2373 * actually possible to rename INBOX but if you do another
2374 * INBOX will immediately be created by the server */
2375 response = camel_imap_command (imap_store, NULL, cancellable, error, "SELECT INBOX");
2381 camel_imap_response_free_without_processing (imap_store, response);
2382 if (imap_store->current_folder)
2383 g_object_unref (imap_store->current_folder);
2384 /* no need to actually create a CamelFolder for INBOX */
2385 imap_store->current_folder = NULL;
2387 imap_store->renaming = TRUE;
2388 if (use_subscriptions)
2389 manage_subscriptions (
2390 store, old_name, FALSE, cancellable);
2392 response = camel_imap_command (imap_store, NULL, cancellable, error, "RENAME %F %F", old_name, new_name_in);
2394 if (use_subscriptions)
2395 manage_subscriptions (
2396 store, old_name, TRUE, cancellable);
2401 camel_imap_response_free (imap_store, response);
2403 /* rename summary, and handle broken server */
2404 rename_folder_info (imap_store, old_name, new_name_in);
2406 if (use_subscriptions)
2407 manage_subscriptions (
2408 store, new_name_in, TRUE, cancellable);
2410 storage_path = g_build_filename (user_cache_dir, "folders", NULL);
2411 oldpath = imap_path_to_physical (storage_path, old_name);
2412 newpath = imap_path_to_physical (storage_path, new_name_in);
2414 /* So do we care if this didn't work? Its just a cache? */
2415 if (g_rename (oldpath, newpath) == -1) {
2416 g_warning ("Could not rename message cache '%s' to '%s': %s: cache reset",
2417 oldpath, newpath, g_strerror (errno));
2420 if (CAMEL_STORE (imap_store)->folders) {
2421 CamelFolder *folder;
2423 folder = camel_object_bag_get (CAMEL_STORE (imap_store)->folders, old_name);
2425 CamelImapFolder *imap_folder = CAMEL_IMAP_FOLDER (folder);
2427 if (imap_folder && imap_folder->journal) {
2428 gchar *folder_dir = imap_path_to_physical (storage_path, new_name_in);
2429 gchar *path = g_strdup_printf ("%s/journal", folder_dir);
2431 camel_offline_journal_set_filename (imap_folder->journal, path);
2434 g_free (folder_dir);
2437 g_object_unref (folder);
2441 g_free (storage_path);
2445 imap_store->renaming = FALSE;
2450 static CamelFolderInfo *
2451 imap_store_create_folder_sync (CamelStore *store,
2452 const gchar *parent_name,
2453 const gchar *folder_name,
2454 GCancellable *cancellable,
2457 CamelImapStore *imap_store = CAMEL_IMAP_STORE (store);
2458 gchar *full_name, *resp, *thisone, *parent_real, *real_name;
2459 CamelImapResponse *response;
2460 CamelFolderInfo *root = NULL;
2461 gboolean need_convert;
2465 if (!camel_offline_store_get_online (CAMEL_OFFLINE_STORE (store))) {
2467 error, CAMEL_SERVICE_ERROR,
2468 CAMEL_SERVICE_ERROR_UNAVAILABLE,
2469 _("You must be working online to complete this operation"));
2477 while (*c && *c != imap_store->dir_sep && !strchr ("#%*", *c))
2482 error, CAMEL_FOLDER_ERROR,
2483 CAMEL_FOLDER_ERROR_INVALID_PATH,
2484 _("The folder name \"%s\" is invalid because it contains the character \"%c\""),
2489 /* check if the parent allows inferiors */
2491 /* FIXME: use storesummary directly */
2492 parent_real = camel_imap_store_summary_full_from_path (imap_store->summary, parent_name);
2493 if (parent_real == NULL) {
2495 error, CAMEL_FOLDER_ERROR,
2496 CAMEL_FOLDER_ERROR_INVALID_STATE,
2497 _("Unknown parent folder: %s"), parent_name);
2501 need_convert = FALSE;
2502 response = camel_imap_command (imap_store, NULL, cancellable, error, "LIST \"\" %G",
2504 if (!response) /* whoa, this is bad */ {
2505 g_free (parent_real);
2509 /* FIXME: does not handle unexpected circumstances very well */
2510 for (i = 0; i < response->untagged->len && !need_convert; i++) {
2511 resp = response->untagged->pdata[i];
2513 if (!imap_parse_list_response (imap_store, resp, &flags, NULL, &thisone))
2516 if (strcmp (thisone, parent_name) == 0) {
2517 if (flags & CAMEL_FOLDER_NOINFERIORS)
2518 need_convert = TRUE;
2524 camel_imap_response_free (imap_store, response);
2526 /* if not, check if we can delete it and recreate it */
2528 struct imap_status_item *items, *item;
2529 guint32 messages = 0;
2532 item = items = get_folder_status (imap_store, parent_name, "MESSAGES");
2533 while (item != NULL) {
2534 if (!g_ascii_strcasecmp (item->name, "MESSAGES")) {
2535 messages = item->value;
2542 imap_status_item_free (items);
2546 error, CAMEL_FOLDER_ERROR,
2547 CAMEL_FOLDER_ERROR_INVALID_STATE,
2548 _("The parent folder is not allowed to contain subfolders"));
2549 g_free (parent_real);
2553 /* delete the old parent and recreate it */
2554 if (!imap_store_delete_folder_sync (store, parent_name, cancellable, error))
2557 /* add the dirsep to the end of parent_name */
2558 name = g_strdup_printf ("%s%c", parent_real, imap_store->dir_sep);
2559 response = camel_imap_command (imap_store, NULL, cancellable, error, "CREATE %G",
2564 g_free (parent_real);
2567 camel_imap_response_free (imap_store, response);
2569 root = imap_build_folder_info (imap_store, parent_name);
2572 /* ok now we can create the folder */
2573 real_name = camel_imap_store_summary_path_to_full (imap_store->summary, folder_name, imap_store->dir_sep);
2574 full_name = imap_concat (imap_store, parent_real, real_name);
2576 response = camel_imap_command (imap_store, NULL, cancellable, error, "CREATE %G", full_name);
2579 CamelImapStoreInfo *si;
2580 CamelFolderInfo *fi;
2582 camel_imap_response_free (imap_store, response);
2584 si = camel_imap_store_summary_add_from_full (imap_store->summary, full_name, imap_store->dir_sep);
2585 camel_store_summary_save ((CamelStoreSummary *) imap_store->summary);
2586 fi = imap_build_folder_info (imap_store, camel_store_info_path (imap_store->summary, si));
2587 fi->flags |= CAMEL_FOLDER_NOCHILDREN;
2594 camel_store_folder_created (store, root);
2596 /* need to re-recreate the folder we just deleted */
2597 camel_store_folder_created (store, root);
2598 camel_folder_info_free (root);
2603 g_free (parent_real);
2608 static CamelFolderInfo *
2609 parse_list_response_as_folder_info (CamelImapStore *imap_store,
2610 const gchar *response)
2612 CamelFolderInfo *fi;
2615 CamelImapStoreInfo *si;
2618 if (!imap_parse_list_response (imap_store, response, &flags, &sep, &dir))
2621 /* FIXME: should use imap_build_folder_info, note the differences with param setting tho */
2623 si = camel_imap_store_summary_add_from_full (imap_store->summary, dir, sep ? sep : '/');
2628 newflags = (si->info.flags & (CAMEL_STORE_INFO_FOLDER_SUBSCRIBED | CAMEL_STORE_INFO_FOLDER_CHECK_FOR_NEW)) | (flags & ~CAMEL_STORE_INFO_FOLDER_SUBSCRIBED);
2629 if (si->info.flags != newflags) {
2630 si->info.flags = newflags;
2631 camel_store_summary_touch ((CamelStoreSummary *) imap_store->summary);
2634 flags = (flags & ~CAMEL_FOLDER_SUBSCRIBED) | (si->info.flags & CAMEL_STORE_FOLDER_INFO_SUBSCRIBED);
2636 fi = camel_folder_info_new ();
2637 fi->full_name = g_strdup (camel_store_info_path (imap_store->summary, si));
2638 if (!g_ascii_strcasecmp(fi->full_name, "inbox")) {
2639 flags |= CAMEL_FOLDER_SYSTEM | CAMEL_FOLDER_TYPE_INBOX;
2640 fi->display_name = g_strdup (_("Inbox"));
2642 fi->display_name = g_strdup (camel_store_info_name (imap_store->summary, si));
2644 /* HACK: some servers report noinferiors for all folders (uw-imapd)
2645 * We just translate this into nochildren, and let the imap layer enforce
2646 * it. See create folder */
2647 if (flags & CAMEL_FOLDER_NOINFERIORS)
2648 flags = (flags & ~CAMEL_FOLDER_NOINFERIORS) | CAMEL_FOLDER_NOCHILDREN;
2657 static gint imap_match_pattern (CamelImapStoreNamespace *ns, const gchar *pattern, const gchar *name)
2659 gchar p, n, dir_sep;
2672 } else if (p == '%') {
2678 } else if (p == '*') {
2684 return n == 0 && (p == '%' || p == 0);
2687 /* imap needs to treat inbox case insensitive */
2688 /* we'll assume the names are normalized already */
2689 static guint folder_hash (gconstpointer ap)
2691 const gchar *a = ap;
2693 if (g_ascii_strcasecmp(a, "INBOX") == 0)
2696 return g_str_hash (a);
2699 static gint folder_eq (gconstpointer ap, gconstpointer bp)
2701 const gchar *a = ap;
2702 const gchar *b = bp;
2704 if (g_ascii_strcasecmp(a, "INBOX") == 0)
2706 if (g_ascii_strcasecmp(b, "INBOX") == 0)
2709 return g_str_equal (a, b);
2713 get_folders_free (gpointer k,
2717 camel_folder_info_free (v);
2721 get_folders_sync (CamelImapStore *imap_store,
2722 const gchar *ppattern,
2723 GCancellable *cancellable,
2726 CamelImapResponse *response;
2727 CamelFolderInfo *fi, *hfi;
2729 gint i, count, j, k;
2730 GHashTable *present;
2732 const gchar *pattern = ppattern;
2733 CamelImapStoreNamespace *ns;
2734 gboolean success = TRUE, first_namespace = TRUE;
2736 /* We do a LIST followed by LSUB, and merge the results. LSUB may not be a strict
2737 * subset of LIST for some servers, so we can't use either or separately */
2738 present = g_hash_table_new (folder_hash, folder_eq);
2743 for (ns = imap_store->summary->namespace; ns; ns = ns->next, first_namespace = FALSE) {
2744 for (k = 0; k < 2; k++) {
2748 if (!ns->full_name || !*ns->full_name) {
2751 tmp = g_strdup ("*");
2753 tmp = g_strdup_printf ("%s%c", ns->full_name, ns->sep);
2755 tmp = g_strdup_printf ("%s%c*", ns->full_name, ns->sep);
2759 for (j = 0; j < 2; j++) {
2760 response = camel_imap_command (imap_store, NULL, cancellable, first_namespace ? error : NULL,
2761 "%s \"\" %G", j==1 ? "LSUB" : "LIST",
2769 for (i = 0; i < response->untagged->len; i++) {
2770 list = response->untagged->pdata[i];
2771 fi = parse_list_response_as_folder_info (imap_store, list);
2772 if (fi && *fi->full_name) {
2773 hfi = g_hash_table_lookup (present, fi->full_name);
2776 fi->flags |= CAMEL_STORE_INFO_FOLDER_SUBSCRIBED;
2777 if ((fi->flags & (CAMEL_IMAP_FOLDER_MARKED | CAMEL_IMAP_FOLDER_UNMARKED)))
2778 imap_store->capabilities |= IMAP_CAPABILITY_useful_lsub;
2780 g_hash_table_insert (present, fi->full_name, fi);
2783 hfi->flags |= CAMEL_STORE_INFO_FOLDER_SUBSCRIBED;
2784 camel_folder_info_free (fi);
2787 camel_folder_info_free (fi);
2791 camel_imap_response_free (imap_store, response);
2796 /* look for matching only, if ppattern was non-NULL */
2802 /* Sync summary to match */
2804 /* FIXME: we need to emit folder_create/subscribed/etc events for any new folders */
2805 count = camel_store_summary_count ((CamelStoreSummary *) imap_store->summary);
2807 for (i = 0; i < count; i++) {
2808 const gchar *full_name;
2810 si = camel_store_summary_index ((CamelStoreSummary *) imap_store->summary, i);
2814 full_name = camel_imap_store_info_full_name (imap_store->summary, si);
2815 if (!full_name || !*full_name) {
2816 camel_store_summary_info_free ((CamelStoreSummary *) imap_store->summary, si);
2820 if (!ppattern || imap_match_pattern (camel_imap_store_summary_namespace_find_full (imap_store->summary, full_name), pattern, full_name)) {
2821 if ((fi = g_hash_table_lookup (present, camel_store_info_path (imap_store->summary, si))) != NULL) {
2822 if (((fi->flags ^ si->flags) & CAMEL_STORE_INFO_FOLDER_SUBSCRIBED)) {
2823 si->flags = (si->flags & ~CAMEL_FOLDER_SUBSCRIBED) | (fi->flags & CAMEL_FOLDER_SUBSCRIBED);
2824 camel_store_summary_touch ((CamelStoreSummary *) imap_store->summary);
2826 camel_store_folder_created (CAMEL_STORE (imap_store), fi);
2827 camel_subscribable_folder_subscribed (CAMEL_SUBSCRIBABLE (imap_store), fi);
2830 gchar *dup_folder_name = g_strdup (camel_store_info_path (imap_store->summary, si));
2832 if (dup_folder_name) {
2833 imap_folder_effectively_unsubscribed (imap_store, dup_folder_name, NULL);
2834 imap_forget_folder (imap_store, dup_folder_name, NULL);
2836 g_free (dup_folder_name);
2838 camel_store_summary_remove ((CamelStoreSummary *) imap_store->summary, si);
2845 camel_store_summary_info_free ((CamelStoreSummary *) imap_store->summary, si);
2848 g_hash_table_foreach (present, get_folders_free, NULL);
2849 g_hash_table_destroy (present);
2856 dumpfi (CamelFolderInfo *fi)
2859 CamelFolderInfo *n = fi;
2871 printf("%-25s %-25s %*s\n", fi->name, fi->full_name, (gint)(depth*2+strlen(fi->uri)), fi->uri);
2880 fill_fi (CamelStore *store,
2881 CamelFolderInfo *fi,
2884 CamelFolder *folder;
2886 folder = camel_object_bag_peek (store->folders, fi->full_name);
2888 CamelImapSummary *ims;
2890 if (folder->summary)
2891 ims = (CamelImapSummary *) folder->summary;
2893 ims = (CamelImapSummary *) camel_imap_summary_new (folder);
2895 fi->unread = camel_folder_summary_get_unread_count ((CamelFolderSummary *) ims);
2896 fi->total = camel_folder_summary_get_saved_count ((CamelFolderSummary *) ims);
2898 if (!folder->summary)
2899 g_object_unref (ims);
2900 g_object_unref (folder);
2905 refresh_refresh (CamelSession *session,
2906 GCancellable *cancellable,
2907 CamelImapStore *store,
2910 CamelService *service;
2911 CamelSettings *settings;
2914 service = CAMEL_SERVICE (store);
2915 settings = camel_service_get_settings (service);
2917 namespace = camel_imap_settings_dup_namespace (
2918 CAMEL_IMAP_SETTINGS (settings));
2920 camel_operation_push_message (cancellable,
2921 _("Retrieving list of folders at '%s'"),
2922 camel_service_get_display_name (service));
2924 if (!camel_imap_store_connected (store, error))
2927 if (namespace != NULL) {
2928 if (!get_folders_sync (store, "INBOX", cancellable, error))
2931 if (!get_folders_sync (store, "*", cancellable, error))
2935 /* look in all namespaces */
2936 get_folders_sync (store, NULL, cancellable, error);
2938 camel_store_summary_save (CAMEL_STORE_SUMMARY (store->summary));
2941 camel_operation_pop_message (cancellable);
2946 static CamelFolderInfo *
2947 imap_store_get_folder_info_sync (CamelStore *store,
2949 CamelStoreGetFolderInfoFlags flags,
2950 GCancellable *cancellable,
2953 CamelImapStore *imap_store = CAMEL_IMAP_STORE (store);
2954 CamelFolderInfo *tree = NULL;
2955 CamelService *service;
2956 CamelSession *session;
2958 service = CAMEL_SERVICE (store);
2959 session = camel_service_get_session (service);
2961 /* If we have a list of folders already, use that, but if we haven't
2962 * updated for a while, then trigger an asynchronous rescan. Otherwise
2963 * we update the list first, and then build it from that */
2968 if (camel_debug("imap:folder_info"))
2969 printf("get folder info online\n");
2971 if (!camel_offline_store_get_online (CAMEL_OFFLINE_STORE (store))) {
2972 tree = get_folder_info_offline (store, top, flags, error);
2976 if ((flags & CAMEL_STORE_FOLDER_INFO_SUBSCRIBED)
2977 && camel_store_summary_count ((CamelStoreSummary *) imap_store->summary) > 0) {
2982 ref = now > imap_store->refresh_stamp + 60 * 60 * 1;
2984 ref = now > imap_store->refresh_stamp + 60 * 60 * 1;
2986 imap_store->refresh_stamp = now;
2988 camel_session_submit_job (
2989 session, (CamelSessionCallback)
2991 g_object_ref (store),
2992 (GDestroyNotify) g_object_unref);
2998 CamelImapStoreNamespace *ns;
3000 if (!camel_imap_store_connected ((CamelImapStore *) store, error))
3004 pattern = g_alloca (3);
3011 name = camel_imap_store_summary_full_from_path (imap_store->summary, top);
3013 name = camel_imap_store_summary_path_to_full (imap_store->summary, top, imap_store->dir_sep);
3016 pattern = g_alloca (i + 5);
3017 strcpy (pattern, name);
3021 ns = camel_imap_store_summary_get_main_namespace (imap_store->summary);
3022 if (!get_folders_sync (imap_store, pattern, cancellable, error))
3024 if (pattern[0] != '*' && ns) {
3025 pattern[i] = ns->sep;
3026 pattern[i + 1] = (flags & CAMEL_STORE_FOLDER_INFO_RECURSIVE) ? '*':'%';
3028 get_folders_sync (imap_store, pattern, cancellable, NULL);
3030 camel_store_summary_save ((CamelStoreSummary *) imap_store->summary);
3033 tree = get_folder_info_offline (store, top, flags, error);
3037 static CamelFolderInfo *
3038 get_folder_info_offline (CamelStore *store,
3043 CamelImapStore *imap_store = CAMEL_IMAP_STORE (store);
3044 gboolean include_inbox = FALSE;
3045 CamelFolderInfo *fi;
3046 CamelService *service;
3047 CamelSettings *settings;
3049 gchar *pattern, *name;
3051 CamelImapStoreNamespace *main_ns, *ns;
3052 gboolean use_subscriptions;
3056 if (camel_debug("imap:folder_info"))
3057 printf("get folder info offline\n");
3059 service = CAMEL_SERVICE (store);
3060 settings = camel_service_get_settings (service);
3062 use_subscriptions = camel_imap_settings_get_use_subscriptions (
3063 CAMEL_IMAP_SETTINGS (settings));
3065 junk_path = camel_imap_settings_dup_real_junk_path (
3066 CAMEL_IMAP_SETTINGS (settings));
3068 /* So we can safely compare strings. */
3069 if (junk_path == NULL)
3070 junk_path = g_strdup ("");
3072 trash_path = camel_imap_settings_dup_real_trash_path (
3073 CAMEL_IMAP_SETTINGS (settings));
3075 /* So we can safely compare strings. */
3076 if (trash_path == NULL)
3077 trash_path = g_strdup ("");
3079 /* FIXME: obey other flags */
3081 folders = g_ptr_array_new ();
3083 if (top == NULL || top[0] == '\0') {
3084 include_inbox = TRUE;
3088 /* get starting point */
3090 name = g_strdup("");
3092 name = camel_imap_store_summary_full_from_path (imap_store->summary, top);
3094 name = camel_imap_store_summary_path_to_full (imap_store->summary, top, imap_store->dir_sep);
3097 main_ns = camel_imap_store_summary_get_main_namespace (imap_store->summary);
3098 pattern = imap_concat(imap_store, name, "*");
3100 /* folder_info_build will insert parent nodes as necessary and mark
3101 * them as noselect, which is information we actually don't have at
3102 * the moment. So let it do the right thing by bailing out if it's
3103 * not a folder we're explicitly interested in. */
3105 for (i = 0; i < camel_store_summary_count ((CamelStoreSummary *) imap_store->summary); i++) {
3106 CamelStoreInfo *si = camel_store_summary_index ((CamelStoreSummary *) imap_store->summary, i);
3107 const gchar *full_name;
3108 gboolean folder_is_junk;
3109 gboolean folder_is_trash;
3114 full_name = camel_imap_store_info_full_name (imap_store->summary, si);
3115 if (!full_name || !*full_name) {
3116 camel_store_summary_info_free ((CamelStoreSummary *) imap_store->summary, si);
3120 ns = camel_imap_store_summary_namespace_find_full (imap_store->summary, full_name);
3122 if ((g_str_equal (name, full_name)
3123 || imap_match_pattern (ns, pattern, full_name)
3124 || (include_inbox && !g_ascii_strcasecmp (full_name, "INBOX")))
3125 && ((ns == main_ns &&
3127 || (flags & CAMEL_STORE_FOLDER_INFO_SUBSCRIBED) == 0))
3128 || (si->flags & CAMEL_STORE_INFO_FOLDER_SUBSCRIBED)
3129 || (flags & CAMEL_STORE_FOLDER_INFO_SUBSCRIPTION_LIST) != 0)) {
3131 fi = imap_build_folder_info (imap_store, camel_store_info_path ((CamelStoreSummary *) imap_store->summary, si));
3132 fi->unread = si->unread;
3133 fi->total = si->total;
3134 fi->flags = si->flags;
3135 /* HACK: some servers report noinferiors for all folders (uw-imapd)
3136 * We just translate this into nochildren, and let the imap layer enforce
3137 * it. See create folder */
3138 if (fi->flags & CAMEL_FOLDER_NOINFERIORS)
3139 fi->flags = (fi->flags & ~CAMEL_FOLDER_NOINFERIORS) | CAMEL_FOLDER_NOCHILDREN;
3141 /* blah, this gets lost somewhere, i can't be bothered finding out why */
3142 if (!g_ascii_strcasecmp(fi->full_name, "inbox"))
3143 fi->flags = (fi->flags & ~CAMEL_FOLDER_TYPE_MASK) | CAMEL_FOLDER_TYPE_INBOX;
3146 (fi->flags & CAMEL_FOLDER_TYPE_MASK) == 0 &&
3147 g_ascii_strcasecmp (fi->full_name, trash_path) == 0;
3148 if (folder_is_trash) {
3149 fi->flags &= ~CAMEL_FOLDER_TYPE_MASK;
3150 fi->flags |= CAMEL_FOLDER_TYPE_TRASH;
3154 (fi->flags & CAMEL_FOLDER_TYPE_MASK) == 0 &&
3155 g_ascii_strcasecmp (fi->full_name, junk_path) == 0;
3157 if (folder_is_junk) {
3158 fi->flags &= ~CAMEL_FOLDER_TYPE_MASK;
3159 fi->flags |= CAMEL_FOLDER_TYPE_JUNK;
3162 if (!(si->flags & CAMEL_FOLDER_NOSELECT))
3163 fill_fi ((CamelStore *) imap_store, fi, 0);
3166 fi->flags |= CAMEL_FOLDER_NOCHILDREN;
3167 g_ptr_array_add (folders, fi);
3169 camel_store_summary_info_free ((CamelStoreSummary *) imap_store->summary, si);
3173 fi = camel_folder_info_build (folders, top, '/', TRUE);
3174 g_ptr_array_free (folders, TRUE);
3178 g_free (trash_path);
3183 /* Use this whenever you need to ensure you're both connected and
3186 camel_imap_store_connected (CamelImapStore *store,
3189 CamelService *service;
3190 CamelOfflineStore *offline_store;
3192 GError *local_error = NULL;
3194 /* This looks stupid ... because it is.
3196 * camel-service-connect will return OK if we connect in 'offline mode',
3197 * which isn't what we want at all. So we have to recheck we actually
3198 * did connect anyway ... */
3200 if (store->istream != NULL)
3203 service = CAMEL_SERVICE (store);
3204 offline_store = CAMEL_OFFLINE_STORE (store);
3207 camel_offline_store_get_online (offline_store) &&
3208 camel_service_connect_sync (service, NULL, &local_error);
3210 if (success && store->istream != NULL)
3213 if (local_error != NULL)
3214 g_propagate_error (error, local_error);
3217 error, CAMEL_SERVICE_ERROR,
3218 CAMEL_SERVICE_ERROR_UNAVAILABLE,
3219 _("You must be working online "
3220 "to complete this operation"));
3226 camel_imap_store_readline (CamelImapStore *store,
3228 GCancellable *cancellable,
3231 CamelStreamBuffer *stream;
3232 gchar linebuf[1024] = {0};
3236 g_return_val_if_fail (CAMEL_IS_IMAP_STORE (store), -1);
3237 g_return_val_if_fail (dest, -1);
3241 /* Check for connectedness. Failed (or cancelled) operations will
3242 * close the connection. We can't expect a read to have any
3243 * meaning if we reconnect, so always set an exception.
3246 if (!camel_imap_store_connected (store, error))
3249 stream = CAMEL_STREAM_BUFFER (store->istream);
3251 ba = g_byte_array_new ();
3252 while ((nread = camel_stream_buffer_gets (stream, linebuf, sizeof (linebuf), cancellable, error)) > 0) {
3253 g_byte_array_append (ba, (const guint8 *) linebuf, nread);
3254 if (linebuf[nread - 1] == '\n')
3261 error, CAMEL_SERVICE_ERROR,
3262 CAMEL_SERVICE_ERROR_UNAVAILABLE,
3263 _("Server unexpectedly disconnected"));
3266 error, _("Server unexpectedly disconnected: "));
3268 /* do not pass cancellable, the connection is gone or
3269 * the cancellable cancelled, thus there will be no I/O */
3270 camel_service_disconnect_sync (
3271 CAMEL_SERVICE (store), FALSE, NULL, NULL);
3272 g_byte_array_free (ba, TRUE);
3276 if (camel_verbose_debug) {
3277 fprintf (stderr, "received: ");
3278 fwrite (ba->data, 1, ba->len, stderr);
3281 /* camel-imap-command.c:imap_read_untagged expects the CRLFs
3282 * to be stripped off and be nul-terminated *sigh* */
3283 nread = ba->len - 1;
3284 ba->data[nread] = '\0';
3285 if (ba->data[nread - 1] == '\r') {
3286 ba->data[nread - 1] = '\0';
3290 *dest = (gchar *) ba->data;
3291 g_byte_array_free (ba, FALSE);
3297 imap_can_refresh_folder (CamelStore *store,
3298 CamelFolderInfo *info,
3301 CamelService *service;
3302 CamelSettings *settings;
3305 gboolean check_subscribed;
3306 gboolean subscribed;
3307 GError *local_error = NULL;
3309 service = CAMEL_SERVICE (store);
3310 settings = camel_service_get_settings (service);
3312 check_all = camel_imap_settings_get_check_all (
3313 CAMEL_IMAP_SETTINGS (settings));
3315 check_subscribed = camel_imap_settings_get_check_subscribed (
3316 CAMEL_IMAP_SETTINGS (settings));
3318 subscribed = ((info->flags & CAMEL_FOLDER_SUBSCRIBED) != 0);
3320 res = CAMEL_STORE_CLASS (camel_imap_store_parent_class)->
3321 can_refresh_folder (store, info, &local_error) ||
3322 check_all || (check_subscribed && subscribed);
3324 if (!res && local_error == NULL && CAMEL_IS_IMAP_STORE (store)) {
3326 CamelStoreSummary *sm = CAMEL_STORE_SUMMARY (((CamelImapStore *)(store))->summary);
3331 si = camel_store_summary_path (sm, info->full_name);
3333 res = (si->flags & CAMEL_STORE_INFO_FOLDER_CHECK_FOR_NEW) != 0 ? TRUE : FALSE;
3335 camel_store_summary_info_free (sm, si);
3339 if (local_error != NULL)
3340 g_propagate_error (error, local_error);