diff options
This commit was manufactured by cvs2svn to create tag 'R3_1'.R3_1
svn path=/tags/R3_1/; revision=21091
Diffstat (limited to 'camel/providers/imap/camel-imap-folder.c')
-rw-r--r-- | camel/providers/imap/camel-imap-folder.c | 2716 |
1 files changed, 0 insertions, 2716 deletions
diff --git a/camel/providers/imap/camel-imap-folder.c b/camel/providers/imap/camel-imap-folder.c deleted file mode 100644 index 619ea1119a..0000000000 --- a/camel/providers/imap/camel-imap-folder.c +++ /dev/null @@ -1,2716 +0,0 @@ -/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ -/* camel-imap-folder.c: class for an imap folder */ - -/* - * Authors: - * Dan Winship <danw@ximian.com> - * Jeffrey Stedfast <fejj@ximian.com> - * - * Copyright (C) 2000, 2001 Ximian, Inc. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of version 2 of the GNU General Public - * License as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 - * USA - */ - - -#ifdef HAVE_CONFIG_H -#include <config.h> -#endif - -#include <stdlib.h> -#include <sys/types.h> -#include <dirent.h> -#include <sys/stat.h> -#include <unistd.h> -#include <errno.h> -#include <string.h> -#include <fcntl.h> -#include <ctype.h> - -#include "e-util/e-path.h" -#include "e-util/e-time-utils.h" - -#include "camel-imap-folder.h" -#include "camel-imap-command.h" -#include "camel-imap-message-cache.h" -#include "camel-imap-private.h" -#include "camel-imap-search.h" -#include "camel-imap-store.h" -#include "camel-imap-summary.h" -#include "camel-imap-utils.h" -#include "camel-imap-wrapper.h" -#include "camel-data-wrapper.h" -#include "camel-disco-diary.h" -#include "camel-exception.h" -#include "camel-mime-filter-crlf.h" -#include "camel-mime-filter-from.h" -#include "camel-mime-message.h" -#include "camel-mime-utils.h" -#include "camel-multipart.h" -#include "camel-multipart-signed.h" -#include "camel-multipart-encrypted.h" -#include "camel-operation.h" -#include "camel-session.h" -#include "camel-stream-buffer.h" -#include "camel-stream-filter.h" -#include "camel-stream-mem.h" -#include "camel-stream.h" -#include "string-utils.h" -#include "camel-private.h" - - -#define d(x) x - -/* set to -1 for infinite size */ -#define UID_SET_LIMIT (4096) - - -#define CF_CLASS(o) (CAMEL_FOLDER_CLASS (CAMEL_OBJECT_GET_CLASS(o))) -static CamelDiscoFolderClass *disco_folder_class = NULL; - -static void imap_finalize (CamelObject *object); -static int imap_getv(CamelObject *object, CamelException *ex, CamelArgGetV *args); - -static void imap_rescan (CamelFolder *folder, int exists, CamelException *ex); -static void imap_refresh_info (CamelFolder *folder, CamelException *ex); -static void imap_sync_online (CamelFolder *folder, CamelException *ex); -static void imap_sync_offline (CamelFolder *folder, CamelException *ex); -static void imap_expunge_uids_online (CamelFolder *folder, GPtrArray *uids, CamelException *ex); -static void imap_expunge_uids_offline (CamelFolder *folder, GPtrArray *uids, CamelException *ex); -static void imap_expunge_uids_resyncing (CamelFolder *folder, GPtrArray *uids, CamelException *ex); -static void imap_cache_message (CamelDiscoFolder *disco_folder, const char *uid, CamelException *ex); -static void imap_rename (CamelFolder *folder, const char *new); - -/* message manipulation */ -static CamelMimeMessage *imap_get_message (CamelFolder *folder, const gchar *uid, - CamelException *ex); -static void imap_append_online (CamelFolder *folder, CamelMimeMessage *message, - const CamelMessageInfo *info, char **appended_uid, - CamelException *ex); -static void imap_append_offline (CamelFolder *folder, CamelMimeMessage *message, - const CamelMessageInfo *info, char **appended_uid, - CamelException *ex); -static void imap_append_resyncing (CamelFolder *folder, CamelMimeMessage *message, - const CamelMessageInfo *info, char **appended_uid, - CamelException *ex); - -static void imap_transfer_online (CamelFolder *source, GPtrArray *uids, - CamelFolder *dest, GPtrArray **transferred_uids, - gboolean delete_originals, - CamelException *ex); -static void imap_transfer_offline (CamelFolder *source, GPtrArray *uids, - CamelFolder *dest, GPtrArray **transferred_uids, - gboolean delete_originals, - CamelException *ex); -static void imap_transfer_resyncing (CamelFolder *source, GPtrArray *uids, - CamelFolder *dest, GPtrArray **transferred_uids, - gboolean delete_originals, - CamelException *ex); - -/* searching */ -static GPtrArray *imap_search_by_expression (CamelFolder *folder, const char *expression, CamelException *ex); -static GPtrArray *imap_search_by_uids (CamelFolder *folder, const char *expression, GPtrArray *uids, CamelException *ex); -static void imap_search_free (CamelFolder *folder, GPtrArray *uids); - -static void imap_thaw (CamelFolder *folder); - -static CamelObjectClass *parent_class; - -static GData *parse_fetch_response (CamelImapFolder *imap_folder, char *msg_att); - -static void -camel_imap_folder_class_init (CamelImapFolderClass *camel_imap_folder_class) -{ - CamelFolderClass *camel_folder_class = CAMEL_FOLDER_CLASS (camel_imap_folder_class); - CamelDiscoFolderClass *camel_disco_folder_class = CAMEL_DISCO_FOLDER_CLASS (camel_imap_folder_class); - - disco_folder_class = CAMEL_DISCO_FOLDER_CLASS (camel_type_get_global_classfuncs (camel_disco_folder_get_type ())); - - /* virtual method overload */ - ((CamelObjectClass *)camel_imap_folder_class)->getv = imap_getv; - - camel_folder_class->get_message = imap_get_message; - camel_folder_class->rename = imap_rename; - camel_folder_class->search_by_expression = imap_search_by_expression; - camel_folder_class->search_by_uids = imap_search_by_uids; - camel_folder_class->search_free = imap_search_free; - camel_folder_class->thaw = imap_thaw; - - camel_disco_folder_class->refresh_info_online = imap_refresh_info; - camel_disco_folder_class->sync_online = imap_sync_online; - camel_disco_folder_class->sync_offline = imap_sync_offline; - /* We don't sync flags at resync time: the online code will - * deal with it eventually. - */ - camel_disco_folder_class->sync_resyncing = imap_sync_offline; - camel_disco_folder_class->expunge_uids_online = imap_expunge_uids_online; - camel_disco_folder_class->expunge_uids_offline = imap_expunge_uids_offline; - camel_disco_folder_class->expunge_uids_resyncing = imap_expunge_uids_resyncing; - camel_disco_folder_class->append_online = imap_append_online; - camel_disco_folder_class->append_offline = imap_append_offline; - camel_disco_folder_class->append_resyncing = imap_append_resyncing; - camel_disco_folder_class->transfer_online = imap_transfer_online; - camel_disco_folder_class->transfer_offline = imap_transfer_offline; - camel_disco_folder_class->transfer_resyncing = imap_transfer_resyncing; - camel_disco_folder_class->cache_message = imap_cache_message; -} - -static void -camel_imap_folder_init (gpointer object, gpointer klass) -{ - CamelImapFolder *imap_folder = CAMEL_IMAP_FOLDER (object); - CamelFolder *folder = CAMEL_FOLDER (object); - - folder->permanent_flags = CAMEL_MESSAGE_ANSWERED | CAMEL_MESSAGE_DELETED | - CAMEL_MESSAGE_DRAFT | CAMEL_MESSAGE_FLAGGED | CAMEL_MESSAGE_SEEN; - - folder->folder_flags |= (CAMEL_FOLDER_HAS_SUMMARY_CAPABILITY | - CAMEL_FOLDER_HAS_SEARCH_CAPABILITY); - - imap_folder->priv = g_malloc0(sizeof(*imap_folder->priv)); -#ifdef ENABLE_THREADS - imap_folder->priv->search_lock = e_mutex_new(E_MUTEX_SIMPLE); - imap_folder->priv->cache_lock = e_mutex_new(E_MUTEX_REC); -#endif - - imap_folder->need_rescan = TRUE; -} - -CamelType -camel_imap_folder_get_type (void) -{ - static CamelType camel_imap_folder_type = CAMEL_INVALID_TYPE; - - if (camel_imap_folder_type == CAMEL_INVALID_TYPE) { - parent_class = camel_disco_folder_get_type(); - camel_imap_folder_type = - camel_type_register (parent_class, "CamelImapFolder", - sizeof (CamelImapFolder), - sizeof (CamelImapFolderClass), - (CamelObjectClassInitFunc) camel_imap_folder_class_init, - NULL, - (CamelObjectInitFunc) camel_imap_folder_init, - (CamelObjectFinalizeFunc) imap_finalize); - } - - return camel_imap_folder_type; -} - -CamelFolder * -camel_imap_folder_new (CamelStore *parent, const char *folder_name, - const char *folder_dir, CamelException *ex) -{ - CamelImapStore *imap_store = CAMEL_IMAP_STORE (parent); - CamelFolder *folder; - CamelImapFolder *imap_folder; - const char *short_name; - char *summary_file; - - if (camel_mkdir_hier (folder_dir, S_IRWXU) != 0) { - camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM, - _("Could not create directory %s: %s"), - folder_dir, g_strerror (errno)); - return NULL; - } - - folder = CAMEL_FOLDER (camel_object_new (camel_imap_folder_get_type ())); - short_name = strrchr (folder_name, imap_store->dir_sep); - if (short_name) - short_name++; - else - short_name = folder_name; - camel_folder_construct (folder, parent, folder_name, short_name); - - summary_file = g_strdup_printf ("%s/summary", folder_dir); - folder->summary = camel_imap_summary_new (summary_file); - g_free (summary_file); - if (!folder->summary) { - camel_object_unref (CAMEL_OBJECT (folder)); - camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM, - _("Could not load summary for %s"), - folder_name); - return NULL; - } - - imap_folder = CAMEL_IMAP_FOLDER (folder); - imap_folder->cache = camel_imap_message_cache_new (folder_dir, folder->summary, ex); - if (!imap_folder->cache) { - camel_object_unref (CAMEL_OBJECT (folder)); - return NULL; - } - - if ((imap_store->parameters & IMAP_PARAM_FILTER_INBOX) && - !strcasecmp (folder_name, "INBOX")) - folder->folder_flags |= CAMEL_FOLDER_FILTER_RECENT; - - imap_folder->search = camel_imap_search_new(folder_dir); - - return folder; -} - -/* Called with the store's connect_lock locked */ -void -camel_imap_folder_selected (CamelFolder *folder, CamelImapResponse *response, - CamelException *ex) -{ - CamelImapFolder *imap_folder = CAMEL_IMAP_FOLDER (folder); - CamelImapSummary *imap_summary = CAMEL_IMAP_SUMMARY (folder->summary); - unsigned long exists = 0, validity = 0, val, uid; - CamelMessageInfo *info; - GData *fetch_data; - int i, count; - char *resp; - - CAMEL_SERVICE_ASSERT_LOCKED (folder->parent_store, connect_lock); - - count = camel_folder_summary_count (folder->summary); - - for (i = 0; i < response->untagged->len; i++) { - resp = response->untagged->pdata[i] + 2; - if (!strncasecmp (resp, "FLAGS ", 6) && - !folder->permanent_flags) { - resp += 6; - folder->permanent_flags = imap_parse_flag_list (&resp); - } else if (!strncasecmp (resp, "OK [PERMANENTFLAGS ", 19)) { - resp += 19; - folder->permanent_flags = imap_parse_flag_list (&resp); - } else if (!strncasecmp (resp, "OK [UIDVALIDITY ", 16)) { - validity = strtoul (resp + 16, NULL, 10); - } else if (isdigit ((unsigned char)*resp)) { - unsigned long num = strtoul (resp, &resp, 10); - - if (!strncasecmp (resp, " EXISTS", 7)) { - exists = num; - /* Remove from the response so nothing - * else tries to interpret it. - */ - g_free (response->untagged->pdata[i]); - g_ptr_array_remove_index (response->untagged, i--); - } - } - } - - if (camel_disco_store_status (CAMEL_DISCO_STORE (folder->parent_store)) == CAMEL_DISCO_STORE_RESYNCING) { - if (validity != imap_summary->validity) { - camel_exception_setv (ex, CAMEL_EXCEPTION_FOLDER_SUMMARY_INVALID, - _("Folder was destroyed and recreated on server.")); - return; - } - - /* FIXME: find missing UIDs ? */ - return; - } - - if (!imap_summary->validity) - imap_summary->validity = validity; - else if (validity != imap_summary->validity) { - imap_summary->validity = validity; - camel_folder_summary_clear (folder->summary); - CAMEL_IMAP_FOLDER_LOCK (imap_folder, cache_lock); - camel_imap_message_cache_clear (imap_folder->cache); - CAMEL_IMAP_FOLDER_UNLOCK (imap_folder, cache_lock); - imap_folder->need_rescan = FALSE; - camel_imap_folder_changed (folder, exists, NULL, ex); - return; - } - - /* If we've lost messages, we have to rescan everything */ - if (exists < count) - imap_folder->need_rescan = TRUE; - else if (count != 0 && !imap_folder->need_rescan) { - CamelImapStore *store = CAMEL_IMAP_STORE (folder->parent_store); - - /* Similarly, if the UID of the highest message we - * know about has changed, then that indicates that - * messages have been both added and removed, so we - * have to rescan to find the removed ones. (We pass - * NULL for the folder since we know that this folder - * is selected, and we don't want camel_imap_command - * to worry about it.) - */ - response = camel_imap_command (store, NULL, ex, "FETCH %d UID", count); - if (!response) - return; - uid = 0; - for (i = 0; i < response->untagged->len; i++) { - resp = response->untagged->pdata[i]; - val = strtoul (resp + 2, &resp, 10); - if (val == 0) - continue; - if (!strcasecmp (resp, " EXISTS")) { - /* Another one?? */ - exists = val; - continue; - } - if (uid != 0 || val != count || strncasecmp (resp, " FETCH (", 8) != 0) - continue; - - fetch_data = parse_fetch_response (imap_folder, resp + 7); - uid = strtoul (g_datalist_get_data (&fetch_data, "UID"), NULL, 10); - g_datalist_clear (&fetch_data); - } - camel_imap_response_free_without_processing (store, response); - - info = camel_folder_summary_index (folder->summary, count - 1); - val = strtoul (camel_message_info_uid (info), NULL, 10); - camel_folder_summary_info_free (folder->summary, info); - if (uid == 0 || uid != val) - imap_folder->need_rescan = TRUE; - } - - /* Now rescan if we need to */ - if (imap_folder->need_rescan) { - imap_rescan (folder, exists, ex); - return; - } - - /* If we don't need to rescan completely, but new messages - * have been added, find out about them. - */ - if (exists > count) - camel_imap_folder_changed (folder, exists, NULL, ex); - - /* And we're done. */ -} - -static void -imap_finalize (CamelObject *object) -{ - CamelImapFolder *imap_folder = CAMEL_IMAP_FOLDER (object); - - if (imap_folder->search) - camel_object_unref (CAMEL_OBJECT (imap_folder->search)); - if (imap_folder->cache) - camel_object_unref (CAMEL_OBJECT (imap_folder->cache)); - -#ifdef ENABLE_THREADS - e_mutex_destroy(imap_folder->priv->search_lock); - e_mutex_destroy(imap_folder->priv->cache_lock); -#endif - g_free(imap_folder->priv); -} - -static int -imap_getv(CamelObject *object, CamelException *ex, CamelArgGetV *args) -{ - CamelFolder *folder = (CamelFolder *)object; - int i, count=args->argc; - guint32 tag; - - for (i=0;i<args->argc;i++) { - CamelArgGet *arg = &args->argv[i]; - - tag = arg->tag; - - switch (tag & CAMEL_ARG_TAG) { - /* CamelObject args */ - case CAMEL_OBJECT_ARG_DESCRIPTION: - if (folder->description == NULL) { - CamelURL *uri = ((CamelService *)folder->parent_store)->url; - - /* what if the full name doesn't incclude /'s? does it matter? */ - folder->description = g_strdup_printf("%s@%s:%s", uri->user, uri->host, folder->full_name); - } - *arg->ca_str = folder->description; - break; - default: - count--; - continue; - } - - arg->tag = (tag & CAMEL_ARG_TYPE) | CAMEL_ARG_IGNORE; - } - - if (count) - return ((CamelObjectClass *)parent_class)->getv(object, ex, args); - - return 0; -} - -static void -imap_rename (CamelFolder *folder, const char *new) -{ - CamelImapFolder *imap_folder = (CamelImapFolder *)folder; - CamelImapStore *imap_store = (CamelImapStore *)folder->parent_store; - char *folder_dir, *summary_path; - char *folders; - - folders = g_strconcat (imap_store->storage_path, "/folders", NULL); - folder_dir = e_path_to_physical (folders, new); - g_free (folders); - summary_path = g_strdup_printf("%s/summary", folder_dir); - - CAMEL_IMAP_FOLDER_LOCK (folder, cache_lock); - camel_imap_message_cache_set_path(imap_folder->cache, folder_dir); - CAMEL_IMAP_FOLDER_UNLOCK (folder, cache_lock); - - camel_folder_summary_set_filename(folder->summary, summary_path); - - g_free(summary_path); - g_free(folder_dir); - - ((CamelFolderClass *)disco_folder_class)->rename(folder, new); -} - -static void -imap_refresh_info (CamelFolder *folder, CamelException *ex) -{ - CamelImapStore *imap_store = CAMEL_IMAP_STORE (folder->parent_store); - CamelImapFolder *imap_folder = CAMEL_IMAP_FOLDER (folder); - CamelImapResponse *response; - - if (camel_disco_store_status (CAMEL_DISCO_STORE (imap_store)) == CAMEL_DISCO_STORE_OFFLINE) - return; - - if (camel_folder_is_frozen (folder)) { - imap_folder->need_refresh = TRUE; - return; - } - - /* If the folder isn't selected, select it (which will force - * a rescan if one is needed). - * Also, if this is the INBOX, some servers (cryus) wont tell - * us with a NOOP of new messages, so force a reselect which - * should do it. */ - CAMEL_SERVICE_LOCK (imap_store, connect_lock); - if (imap_store->current_folder != folder - || strcasecmp(folder->full_name, "INBOX") == 0) { - CAMEL_SERVICE_UNLOCK (imap_store, connect_lock); - response = camel_imap_command (imap_store, folder, ex, NULL); - if (response) { - camel_imap_folder_selected (folder, response, ex); - camel_imap_response_free (imap_store, response); - } - return; - } - CAMEL_SERVICE_UNLOCK (imap_store, connect_lock); - - /* Otherwise, if we need a rescan, do it, and if not, just do - * a NOOP to give the server a chance to tell us about new - * messages. - */ - if (imap_folder->need_rescan) - imap_rescan (folder, camel_folder_summary_count (folder->summary), ex); - else { -#if 0 - /* on some servers need to CHECKpoint INBOX to recieve new messages?? */ - /* rfc2060 suggests this, but havent seen a server that requires it */ - if (strcasecmp(folder->full_name, "INBOX") == 0) { - response = camel_imap_command (imap_store, folder, ex, "CHECK"); - camel_imap_response_free (imap_store, response); - } -#endif - response = camel_imap_command (imap_store, folder, ex, "NOOP"); - camel_imap_response_free (imap_store, response); - } -} - -/* Called with the store's connect_lock locked */ -static void -imap_rescan (CamelFolder *folder, int exists, CamelException *ex) -{ - CamelImapFolder *imap_folder = CAMEL_IMAP_FOLDER (folder); - CamelImapStore *store = CAMEL_IMAP_STORE (folder->parent_store); - struct { - char *uid; - guint32 flags; - } *new; - char *resp; - CamelImapResponseType type; - int i, seq, summary_len, summary_got; - CamelMessageInfo *info; - CamelImapMessageInfo *iinfo; - GArray *removed; - gboolean ok; - CamelFolderChangeInfo *changes = NULL; - - CAMEL_SERVICE_ASSERT_LOCKED (store, connect_lock); - imap_folder->need_rescan = FALSE; - - summary_len = camel_folder_summary_count (folder->summary); - if (summary_len == 0) { - if (exists) - camel_imap_folder_changed (folder, exists, NULL, ex); - return; - } - - /* Check UIDs and flags of all messages we already know of. */ - camel_operation_start (NULL, _("Scanning for changed messages")); - info = camel_folder_summary_index (folder->summary, summary_len - 1); - ok = camel_imap_command_start (store, folder, ex, - "UID FETCH 1:%s (FLAGS)", - camel_message_info_uid (info)); - camel_folder_summary_info_free (folder->summary, info); - if (!ok) { - camel_operation_end (NULL); - return; - } - - new = g_malloc0 (summary_len * sizeof (*new)); - summary_got = 0; - while ((type = camel_imap_command_response (store, &resp, ex)) == CAMEL_IMAP_RESPONSE_UNTAGGED) { - GData *data; - char *uid; - guint32 flags; - - data = parse_fetch_response (imap_folder, resp); - g_free (resp); - if (!data) - continue; - - seq = GPOINTER_TO_INT (g_datalist_get_data (&data, "SEQUENCE")); - uid = g_datalist_get_data (&data, "UID"); - flags = GPOINTER_TO_UINT (g_datalist_get_data (&data, "FLAGS")); - - if (!uid || !seq || seq > summary_len) { - g_datalist_clear (&data); - continue; - } - - camel_operation_progress (NULL, ++summary_got * 100 / summary_len); - new[seq - 1].uid = g_strdup (uid); - new[seq - 1].flags = flags; - g_datalist_clear (&data); - } - - camel_operation_end (NULL); - if (type == CAMEL_IMAP_RESPONSE_ERROR) { - for (i = 0; i < summary_len && new[i].uid; i++) - g_free (new[i].uid); - g_free (new); - return; - } - - /* Free the final tagged response */ - g_free (resp); - - /* If we find a UID in the summary that doesn't correspond to - * the UID in the folder, then either: (a) it's a real UID, - * but the message was deleted on the server, or (b) it's a - * fake UID, and needs to be removed from the summary in order - * to sync up with the server. So either way, we remove it - * from the summary. - */ - removed = g_array_new (FALSE, FALSE, sizeof (int)); - for (i = 0; i < summary_len && new[i].uid; i++) { - info = camel_folder_summary_index (folder->summary, i); - iinfo = (CamelImapMessageInfo *)info; - - if (strcmp (camel_message_info_uid (info), new[i].uid) != 0) { - camel_folder_summary_info_free(folder->summary, info); - seq = i + 1; - g_array_append_val (removed, seq); - i--; - summary_len--; - continue; - } - - /* Update summary flags */ - if (new[i].flags != iinfo->server_flags) { - guint32 server_set, server_cleared; - - server_set = new[i].flags & ~iinfo->server_flags; - server_cleared = iinfo->server_flags & ~new[i].flags; - - info->flags = (info->flags | server_set) & ~server_cleared; - iinfo->server_flags = new[i].flags; - - if (changes == NULL) - changes = camel_folder_change_info_new(); - camel_folder_change_info_change_uid(changes, new[i].uid); - } - - camel_folder_summary_info_free (folder->summary, info); - g_free (new[i].uid); - } - - if (changes) { - camel_object_trigger_event(CAMEL_OBJECT (folder), "folder_changed", changes); - camel_folder_change_info_free(changes); - } - - seq = i + 1; - - /* Free remaining memory. */ - while (i < summary_len && new[i].uid) - g_free (new[i++].uid); - g_free (new); - - /* Remove any leftover cached summary messages. (Yes, we - * repeatedly add the same number to the removed array. - * See RFC2060 7.4.1) - */ - - for (i = seq; i <= summary_len; i++) - g_array_append_val (removed, seq); - - /* And finally update the summary. */ - camel_imap_folder_changed (folder, exists, removed, ex); - g_array_free (removed, TRUE); -} - -/* the max number of chars that an unsigned 32-bit int can be is 10 chars plus 1 for a possible : */ -#define UID_SET_FULL(setlen, maxlen) (maxlen > 0 ? setlen + 11 >= maxlen : FALSE) - -/* Find all messages in @folder with flags matching @flags and @mask. - * If no messages match, returns %NULL. Otherwise, returns an array of - * CamelMessageInfo and sets *@set to a message set corresponding the - * UIDs of the matched messages (up to @UID_SET_LIMIT bytes). The - * caller must free the infos, the array, and the set string. - */ -static GPtrArray * -get_matching (CamelFolder *folder, guint32 flags, guint32 mask, char **set) -{ - GPtrArray *matches; - CamelMessageInfo *info; - int i, max, range; - GString *gset; - - matches = g_ptr_array_new (); - gset = g_string_new (""); - max = camel_folder_summary_count (folder->summary); - range = -1; - for (i = 0; i < max && !UID_SET_FULL (gset->len, UID_SET_LIMIT); i++) { - info = camel_folder_summary_index (folder->summary, i); - if (!info) - continue; - if ((info->flags & mask) != flags) { - camel_folder_summary_info_free (folder->summary, info); - if (range != -1) { - if (range != i - 1) { - info = matches->pdata[matches->len - 1]; - g_string_append_printf (gset, ":%s", camel_message_info_uid (info)); - } - range = -1; - } - continue; - } - - g_ptr_array_add (matches, info); - if (range != -1) - continue; - range = i; - if (gset->len) - g_string_append_c (gset, ','); - g_string_append_printf (gset, "%s", camel_message_info_uid (info)); - } - - if (range != -1 && range != max - 1) { - info = matches->pdata[matches->len - 1]; - g_string_append_printf (gset, ":%s", camel_message_info_uid (info)); - } - - if (matches->len) { - *set = gset->str; - g_string_free (gset, FALSE); - return matches; - } else { - *set = NULL; - g_string_free (gset, TRUE); - g_ptr_array_free (matches, TRUE); - return NULL; - } -} - -static void -imap_sync_offline (CamelFolder *folder, CamelException *ex) -{ - camel_folder_summary_save (folder->summary); -} - -static void -imap_sync_online (CamelFolder *folder, CamelException *ex) -{ - CamelImapStore *store = CAMEL_IMAP_STORE (folder->parent_store); - CamelImapResponse *response = NULL; - CamelMessageInfo *info; - CamelException local_ex; - GPtrArray *matches; - char *set, *flaglist; - gboolean unset; - int i, j, max; - - camel_exception_init (&local_ex); - CAMEL_SERVICE_LOCK (store, connect_lock); - - /* Find a message with changed flags, find all of the other - * messages like it, sync them as a group, mark them as - * updated, and continue. - */ - max = camel_folder_summary_count (folder->summary); - for (i = 0; i < max; i++) { - info = camel_folder_summary_index (folder->summary, i); - if (!info) - continue; - if (!(info->flags & CAMEL_MESSAGE_FOLDER_FLAGGED)) { - camel_folder_summary_info_free (folder->summary, info); - continue; - } - - /* Note: Cyrus is broken and will not accept an - empty-set of flags so... if this is true then we - want to unset the previously set flags.*/ - unset = !(info->flags & CAMEL_IMAP_SERVER_FLAGS); - - /* Note: get_matching() uses UID_SET_LIMIT to limit - the size of the uid-set string. We don't have to - loop here to flush all the matching uids because - they will be scooped up later by our parent loop (I - think?). -- Jeff */ - matches = get_matching (folder, info->flags & (CAMEL_IMAP_SERVER_FLAGS | CAMEL_MESSAGE_FOLDER_FLAGGED), - CAMEL_IMAP_SERVER_FLAGS | CAMEL_MESSAGE_FOLDER_FLAGGED, &set); - camel_folder_summary_info_free (folder->summary, info); - if (matches == NULL) - continue; - - /* FIXME: since we don't know the previously set flags, - if unset is TRUE then just unset all the flags? */ - flaglist = imap_create_flag_list (unset ? CAMEL_IMAP_SERVER_FLAGS : info->flags); - - /* Note: to `unset' flags, use -FLAGS.SILENT (<flag list>) */ - response = camel_imap_command (store, folder, &local_ex, - "UID STORE %s %sFLAGS.SILENT %s", - set, unset ? "-" : "", flaglist); - g_free (set); - g_free (flaglist); - - if (response) - camel_imap_response_free (store, response); - - if (!camel_exception_is_set (&local_ex)) { - for (j = 0; j < matches->len; j++) { - info = matches->pdata[j]; - info->flags &= ~CAMEL_MESSAGE_FOLDER_FLAGGED; - ((CamelImapMessageInfo*)info)->server_flags = - info->flags & CAMEL_IMAP_SERVER_FLAGS; - } - camel_folder_summary_touch (folder->summary); - } - - for (j = 0; j < matches->len; j++) { - info = matches->pdata[j]; - camel_folder_summary_info_free (folder->summary, info); - } - g_ptr_array_free (matches, TRUE); - - /* We unlock here so that other threads can have a chance to grab the connect_lock */ - CAMEL_SERVICE_UNLOCK (store, connect_lock); - - /* check for an exception */ - if (camel_exception_is_set (&local_ex)) { - camel_exception_xfer (ex, &local_ex); - return; - } - - /* Re-lock the connect_lock */ - CAMEL_SERVICE_LOCK (store, connect_lock); - } - - /* Save the summary */ - imap_sync_offline (folder, ex); - - CAMEL_SERVICE_UNLOCK (store, connect_lock); -} - -static void -imap_expunge_uids_offline (CamelFolder *folder, GPtrArray *uids, CamelException *ex) -{ - CamelFolderChangeInfo *changes; - int i; - - changes = camel_folder_change_info_new (); - - for (i = 0; i < uids->len; i++) { - camel_folder_summary_remove_uid (folder->summary, uids->pdata[i]); - camel_folder_change_info_remove_uid (changes, uids->pdata[i]); - /* We intentionally don't remove it from the cache because - * the cached data may be useful in replaying a COPY later. - */ - } - camel_folder_summary_save (folder->summary); - - camel_disco_diary_log (CAMEL_DISCO_STORE (folder->parent_store)->diary, - CAMEL_DISCO_DIARY_FOLDER_EXPUNGE, folder, uids); - - camel_object_trigger_event (CAMEL_OBJECT (folder), "folder_changed", changes); - camel_folder_change_info_free (changes); -} - -static void -imap_expunge_uids_online (CamelFolder *folder, GPtrArray *uids, CamelException *ex) -{ - CamelImapStore *store = CAMEL_IMAP_STORE (folder->parent_store); - CamelImapResponse *response; - int uid = 0; - char *set; - - CAMEL_SERVICE_LOCK (store, connect_lock); - - if ((store->capabilities & IMAP_CAPABILITY_UIDPLUS) == 0) { - ((CamelFolderClass *)CAMEL_OBJECT_GET_CLASS(folder))->sync(folder, 0, ex); - if (camel_exception_is_set(ex)) { - CAMEL_SERVICE_UNLOCK (store, connect_lock); - return; - } - } - - while (uid < uids->len) { - set = imap_uid_array_to_set (folder->summary, uids, uid, UID_SET_LIMIT, &uid); - response = camel_imap_command (store, folder, ex, - "UID STORE %s +FLAGS.SILENT \\Deleted", - set); - if (response) - camel_imap_response_free (store, response); - if (camel_exception_is_set (ex)) { - CAMEL_SERVICE_UNLOCK (store, connect_lock); - g_free (set); - return; - } - - if (store->capabilities & IMAP_CAPABILITY_UIDPLUS) { - response = camel_imap_command (store, folder, ex, - "UID EXPUNGE %s", set); - } else - response = camel_imap_command (store, folder, ex, "EXPUNGE"); - - if (response) - camel_imap_response_free (store, response); - } - - CAMEL_SERVICE_UNLOCK (store, connect_lock); -} - -static int -uid_compar (const void *va, const void *vb) -{ - const char **sa = (const char **)va, **sb = (const char **)vb; - unsigned long a, b; - - a = strtoul (*sa, NULL, 10); - b = strtoul (*sb, NULL, 10); - if (a < b) - return -1; - else if (a == b) - return 0; - else - return 1; -} - -static void -imap_expunge_uids_resyncing (CamelFolder *folder, GPtrArray *uids, CamelException *ex) -{ - CamelImapStore *store = CAMEL_IMAP_STORE (folder->parent_store); - GPtrArray *keep_uids, *mark_uids; - CamelImapResponse *response; - char *result; - - if (store->capabilities & IMAP_CAPABILITY_UIDPLUS) { - imap_expunge_uids_online (folder, uids, ex); - return; - } - - /* If we don't have UID EXPUNGE we need to avoid expunging any - * of the wrong messages. So we search for deleted messages, - * and any that aren't in our to-expunge list get temporarily - * marked un-deleted. - */ - - CAMEL_SERVICE_LOCK (store, connect_lock); - - ((CamelFolderClass *)CAMEL_OBJECT_GET_CLASS(folder))->sync(folder, 0, ex); - if (camel_exception_is_set(ex)) { - CAMEL_SERVICE_UNLOCK (store, connect_lock); - return; - } - - response = camel_imap_command (store, folder, ex, "UID SEARCH DELETED"); - if (!response) { - CAMEL_SERVICE_UNLOCK (store, connect_lock); - return; - } - result = camel_imap_response_extract (store, response, "SEARCH", ex); - if (!result) { - CAMEL_SERVICE_UNLOCK (store, connect_lock); - return; - } - - if (result[8] == ' ') { - char *uid, *lasts = NULL; - unsigned long euid, kuid; - int ei, ki; - - keep_uids = g_ptr_array_new (); - mark_uids = g_ptr_array_new (); - - /* Parse SEARCH response */ - for (uid = strtok_r (result + 9, " ", &lasts); uid; uid = strtok_r (NULL, " ", &lasts)) - g_ptr_array_add (keep_uids, uid); - qsort (keep_uids->pdata, keep_uids->len, - sizeof (void *), uid_compar); - - /* Fill in "mark_uids", empty out "keep_uids" as needed */ - for (ei = ki = 0; ei < uids->len; ei++) { - euid = strtoul (uids->pdata[ei], NULL, 10); - - for (kuid = 0; ki < keep_uids->len; ki++) { - kuid = strtoul (keep_uids->pdata[ki], NULL, 10); - - if (kuid >= euid) - break; - } - - if (euid == kuid) - g_ptr_array_remove_index (keep_uids, ki); - else - g_ptr_array_add (mark_uids, uids->pdata[ei]); - } - } else { - /* Empty SEARCH result, meaning nothing is marked deleted - * on server. - */ - - keep_uids = NULL; - mark_uids = uids; - } - - /* Unmark messages to be kept */ - - if (keep_uids) { - char *uidset; - int uid = 0; - - while (uid < keep_uids->len) { - uidset = imap_uid_array_to_set (folder->summary, keep_uids, uid, UID_SET_LIMIT, &uid); - - response = camel_imap_command (store, folder, ex, - "UID STORE %s -FLAGS.SILENT \\Deleted", - uidset); - - g_free (uidset); - - if (!response) { - g_ptr_array_free (keep_uids, TRUE); - g_ptr_array_free (mark_uids, TRUE); - CAMEL_SERVICE_UNLOCK (store, connect_lock); - return; - } - camel_imap_response_free (store, response); - } - } - - /* Mark any messages that still need to be marked */ - if (mark_uids) { - char *uidset; - int uid = 0; - - while (uid < mark_uids->len) { - uidset = imap_uid_array_to_set (folder->summary, mark_uids, uid, UID_SET_LIMIT, &uid); - - response = camel_imap_command (store, folder, ex, - "UID STORE %s +FLAGS.SILENT \\Deleted", - uidset); - - g_free (uidset); - - if (!response) { - g_ptr_array_free (keep_uids, TRUE); - g_ptr_array_free (mark_uids, TRUE); - CAMEL_SERVICE_UNLOCK (store, connect_lock); - return; - } - camel_imap_response_free (store, response); - } - - if (mark_uids != uids) - g_ptr_array_free (mark_uids, TRUE); - } - - /* Do the actual expunging */ - response = camel_imap_command (store, folder, ex, "EXPUNGE"); - if (response) - camel_imap_response_free (store, response); - - /* And fix the remaining messages if we mangled them */ - if (keep_uids) { - char *uidset; - int uid = 0; - - while (uid < keep_uids->len) { - uidset = imap_uid_array_to_set (folder->summary, keep_uids, uid, UID_SET_LIMIT, &uid); - - /* Don't pass ex if it's already been set */ - response = camel_imap_command (store, folder, - camel_exception_is_set (ex) ? NULL : ex, - "UID STORE %s +FLAGS.SILENT \\Deleted", - uidset); - - g_free (uidset); - if (response) - camel_imap_response_free (store, response); - } - - g_ptr_array_free (keep_uids, TRUE); - } - - /* now we can free this, now that we're done with keep_uids */ - g_free (result); - - CAMEL_SERVICE_UNLOCK (store, connect_lock); -} - -static gchar * -get_temp_uid (void) -{ - gchar *res; - - static int counter = 0; - G_LOCK_DEFINE_STATIC (lock); - - G_LOCK (lock); - res = g_strdup_printf ("tempuid-%lx-%d", - (unsigned long) time (NULL), - counter++); - G_UNLOCK (lock); - - return res; -} - -static void -imap_append_offline (CamelFolder *folder, CamelMimeMessage *message, - const CamelMessageInfo *info, char **appended_uid, - CamelException *ex) -{ - CamelImapStore *imap_store = CAMEL_IMAP_STORE (folder->parent_store); - CamelImapMessageCache *cache = CAMEL_IMAP_FOLDER (folder)->cache; - CamelFolderChangeInfo *changes; - char *uid; - - uid = get_temp_uid (); - - camel_imap_summary_add_offline (folder->summary, uid, message, info); - CAMEL_IMAP_FOLDER_LOCK (folder, cache_lock); - camel_imap_message_cache_insert_wrapper (cache, uid, "", - CAMEL_DATA_WRAPPER (message), ex); - CAMEL_IMAP_FOLDER_UNLOCK (folder, cache_lock); - - changes = camel_folder_change_info_new (); - camel_folder_change_info_add_uid (changes, uid); - camel_object_trigger_event (CAMEL_OBJECT (folder), "folder_changed", - changes); - camel_folder_change_info_free (changes); - - camel_disco_diary_log (CAMEL_DISCO_STORE (imap_store)->diary, - CAMEL_DISCO_DIARY_FOLDER_APPEND, folder, uid); - if (appended_uid) - *appended_uid = uid; - else - g_free (uid); -} - -static CamelImapResponse * -do_append (CamelFolder *folder, CamelMimeMessage *message, - const CamelMessageInfo *info, char **uid, - CamelException *ex) -{ - CamelImapStore *store = CAMEL_IMAP_STORE (folder->parent_store); - CamelImapResponse *response; - CamelStream *memstream; - CamelMimeFilter *crlf_filter; - CamelStreamFilter *streamfilter; - GByteArray *ba; - char *flagstr, *result, *end; - - /* create flag string param */ - if (info && info->flags) - flagstr = imap_create_flag_list (info->flags); - else - flagstr = NULL; - - /* encode any 8bit parts so we avoid sending embedded nul-chars and such */ - camel_mime_message_encode_8bit_parts (message); - - /* FIXME: We could avoid this if we knew how big the message was. */ - memstream = camel_stream_mem_new (); - ba = g_byte_array_new (); - camel_stream_mem_set_byte_array (CAMEL_STREAM_MEM (memstream), ba); - - streamfilter = camel_stream_filter_new_with_stream (memstream); - crlf_filter = camel_mime_filter_crlf_new (CAMEL_MIME_FILTER_CRLF_ENCODE, - CAMEL_MIME_FILTER_CRLF_MODE_CRLF_ONLY); - camel_stream_filter_add (streamfilter, crlf_filter); - camel_data_wrapper_write_to_stream (CAMEL_DATA_WRAPPER (message), - CAMEL_STREAM (streamfilter)); - camel_object_unref (CAMEL_OBJECT (streamfilter)); - camel_object_unref (CAMEL_OBJECT (crlf_filter)); - camel_object_unref (CAMEL_OBJECT (memstream)); - - response = camel_imap_command (store, NULL, ex, "APPEND %F%s%s {%d}", - folder->full_name, flagstr ? " " : "", - flagstr ? flagstr : "", ba->len); - g_free (flagstr); - - if (!response) { - g_byte_array_free (ba, TRUE); - return NULL; - } - - result = camel_imap_response_extract_continuation (store, response, ex); - if (!result) { - g_byte_array_free (ba, TRUE); - return NULL; - } - g_free (result); - - /* send the rest of our data - the mime message */ - response = camel_imap_command_continuation (store, ba->data, ba->len, ex); - g_byte_array_free (ba, TRUE); - if (!response) - return response; - - if (store->capabilities & IMAP_CAPABILITY_UIDPLUS) { - *uid = camel_strstrcase (response->status, "[APPENDUID "); - if (*uid) - *uid = strchr (*uid + 11, ' '); - if (*uid) { - *uid = g_strndup (*uid + 1, strcspn (*uid + 1, "]")); - /* Make sure it's a number */ - if (strtoul (*uid, &end, 10) == 0 || *end) { - g_free (*uid); - *uid = NULL; - } - } - } else - *uid = NULL; - - return response; -} - -static void -imap_append_online (CamelFolder *folder, CamelMimeMessage *message, - const CamelMessageInfo *info, char **appended_uid, - CamelException *ex) -{ - CamelImapStore *store = CAMEL_IMAP_STORE (folder->parent_store); - CamelImapResponse *response; - char *uid; - int count; - - count = camel_folder_summary_count (folder->summary); - response = do_append (folder, message, info, &uid, ex); - if (!response) - return; - - if (uid) { - /* Cache first, since freeing response may trigger a - * summary update that will want this information. - */ - CAMEL_IMAP_FOLDER_LOCK (folder, cache_lock); - camel_imap_message_cache_insert_wrapper ( - CAMEL_IMAP_FOLDER (folder)->cache, uid, - "", CAMEL_DATA_WRAPPER (message), ex); - CAMEL_IMAP_FOLDER_UNLOCK (folder, cache_lock); - if (appended_uid) - *appended_uid = uid; - else - g_free (uid); - } else if (appended_uid) - *appended_uid = NULL; - - camel_imap_response_free (store, response); - - /* Make sure a "folder_changed" is emitted. */ - CAMEL_SERVICE_LOCK (store, connect_lock); - if (store->current_folder != folder || - camel_folder_summary_count (folder->summary) == count) - imap_refresh_info (folder, ex); - CAMEL_SERVICE_UNLOCK (store, connect_lock); -} - -static void -imap_append_resyncing (CamelFolder *folder, CamelMimeMessage *message, - const CamelMessageInfo *info, char **appended_uid, - CamelException *ex) -{ - CamelImapStore *store = CAMEL_IMAP_STORE (folder->parent_store); - CamelImapResponse *response; - char *uid; - - response = do_append (folder, message, info, &uid, ex); - if (!response) - return; - - if (uid) { - CamelImapFolder *imap_folder = CAMEL_IMAP_FOLDER (folder); - const char *olduid = camel_message_info_uid (info); - - CAMEL_IMAP_FOLDER_LOCK (imap_folder, cache_lock); - camel_imap_message_cache_copy (imap_folder->cache, olduid, - imap_folder->cache, uid, ex); - CAMEL_IMAP_FOLDER_UNLOCK (imap_folder, cache_lock); - - if (appended_uid) - *appended_uid = uid; - else - g_free (uid); - } else if (appended_uid) - *appended_uid = NULL; - - camel_imap_response_free (store, response); -} - - -static void -imap_transfer_offline (CamelFolder *source, GPtrArray *uids, - CamelFolder *dest, GPtrArray **transferred_uids, - gboolean delete_originals, CamelException *ex) -{ - CamelImapStore *store = CAMEL_IMAP_STORE (source->parent_store); - CamelImapMessageCache *sc = CAMEL_IMAP_FOLDER (source)->cache; - CamelImapMessageCache *dc = CAMEL_IMAP_FOLDER (dest)->cache; - CamelFolderChangeInfo *changes; - CamelMimeMessage *message; - CamelMessageInfo *mi; - char *uid, *destuid; - int i; - - /* We grab the store's command lock first, and then grab the - * source and destination cache_locks. This way we can't - * deadlock in the case where we're simultaneously also trying - * to copy messages in the other direction from another thread. - */ - CAMEL_SERVICE_LOCK (store, connect_lock); - CAMEL_IMAP_FOLDER_LOCK (source, cache_lock); - CAMEL_IMAP_FOLDER_LOCK (dest, cache_lock); - CAMEL_SERVICE_UNLOCK (store, connect_lock); - - if (transferred_uids) { - *transferred_uids = g_ptr_array_new (); - g_ptr_array_set_size (*transferred_uids, uids->len); - } - - changes = camel_folder_change_info_new (); - - for (i = 0; i < uids->len; i++) { - uid = uids->pdata[i]; - - destuid = get_temp_uid (); - - mi = camel_folder_summary_uid (source->summary, uid); - g_return_if_fail (mi != NULL); - - message = camel_folder_get_message (source, uid, NULL); - - if (message) { - camel_imap_summary_add_offline (dest->summary, destuid, message, mi); - camel_object_unref (CAMEL_OBJECT (message)); - } else - camel_imap_summary_add_offline_uncached (dest->summary, destuid, mi); - - camel_imap_message_cache_copy (sc, uid, dc, destuid, ex); - camel_folder_summary_info_free (source->summary, mi); - - camel_folder_change_info_add_uid (changes, destuid); - if (transferred_uids) - (*transferred_uids)->pdata[i] = destuid; - else - g_free (destuid); - - if (delete_originals) - camel_folder_delete_message (source, uid); - } - - CAMEL_IMAP_FOLDER_UNLOCK (dest, cache_lock); - CAMEL_IMAP_FOLDER_UNLOCK (source, cache_lock); - - camel_object_trigger_event (CAMEL_OBJECT (dest), "folder_changed", changes); - camel_folder_change_info_free (changes); - - camel_disco_diary_log (CAMEL_DISCO_STORE (store)->diary, - CAMEL_DISCO_DIARY_FOLDER_TRANSFER, - source, dest, uids, delete_originals); -} - -static void -handle_copyuid (CamelImapResponse *response, CamelFolder *source, - CamelFolder *destination) -{ - CamelImapMessageCache *scache = CAMEL_IMAP_FOLDER (source)->cache; - CamelImapMessageCache *dcache = CAMEL_IMAP_FOLDER (destination)->cache; - char *validity, *srcset, *destset; - GPtrArray *src, *dest; - int i; - - validity = camel_strstrcase (response->status, "[COPYUID "); - if (!validity) - return; - validity += 9; - if (strtoul (validity, NULL, 10) != - CAMEL_IMAP_SUMMARY (destination->summary)->validity) - return; - - srcset = strchr (validity, ' '); - if (!srcset++) - goto lose; - destset = strchr (srcset, ' '); - if (!destset++) - goto lose; - - src = imap_uid_set_to_array (source->summary, srcset); - dest = imap_uid_set_to_array (destination->summary, destset); - - if (src && dest && src->len == dest->len) { - /* We don't have to worry about deadlocking on the - * cache locks here, because we've got the store's - * command lock too, so no one else could be here. - */ - CAMEL_IMAP_FOLDER_LOCK (source, cache_lock); - CAMEL_IMAP_FOLDER_LOCK (destination, cache_lock); - for (i = 0; i < src->len; i++) { - camel_imap_message_cache_copy (scache, src->pdata[i], - dcache, dest->pdata[i], - NULL); - } - CAMEL_IMAP_FOLDER_UNLOCK (source, cache_lock); - CAMEL_IMAP_FOLDER_UNLOCK (destination, cache_lock); - - imap_uid_array_free (src); - imap_uid_array_free (dest); - return; - } - - imap_uid_array_free (src); - imap_uid_array_free (dest); - lose: - g_warning ("Bad COPYUID response from server"); -} - -static void -do_copy (CamelFolder *source, GPtrArray *uids, - CamelFolder *destination, CamelException *ex) -{ - CamelImapStore *store = CAMEL_IMAP_STORE (source->parent_store); - CamelImapResponse *response; - char *uidset; - int uid = 0; - - while (uid < uids->len && !camel_exception_is_set (ex)) { - uidset = imap_uid_array_to_set (source->summary, uids, uid, UID_SET_LIMIT, &uid); - - response = camel_imap_command (store, source, ex, "UID COPY %s %F", - uidset, destination->full_name); - - g_free (uidset); - - if (response && (store->capabilities & IMAP_CAPABILITY_UIDPLUS)) - handle_copyuid (response, source, destination); - - camel_imap_response_free (store, response); - } -} - -static void -imap_transfer_online (CamelFolder *source, GPtrArray *uids, - CamelFolder *dest, GPtrArray **transferred_uids, - gboolean delete_originals, CamelException *ex) -{ - CamelImapStore *store = CAMEL_IMAP_STORE (source->parent_store); - int count, i; - - /* Sync message flags if needed. */ - imap_sync_online (source, ex); - if (camel_exception_is_set (ex)) - return; - - count = camel_folder_summary_count (dest->summary); - - /* Now copy the messages */ - do_copy (source, uids, dest, ex); - if (camel_exception_is_set (ex)) - return; - - /* Make the destination notice its new messages */ - CAMEL_SERVICE_LOCK (store, connect_lock); - if (store->current_folder != dest || - camel_folder_summary_count (dest->summary) == count) - camel_folder_refresh_info (dest, ex); - CAMEL_SERVICE_UNLOCK (store, connect_lock); - - if (delete_originals) { - for (i = 0; i < uids->len; i++) - camel_folder_delete_message (source, uids->pdata[i]); - } - - /* FIXME */ - if (transferred_uids) - *transferred_uids = NULL; -} - -static void -imap_transfer_resyncing (CamelFolder *source, GPtrArray *uids, - CamelFolder *dest, GPtrArray **transferred_uids, - gboolean delete_originals, CamelException *ex) -{ - CamelDiscoDiary *diary = CAMEL_DISCO_STORE (source->parent_store)->diary; - GPtrArray *realuids; - int first, i; - const char *uid; - CamelMimeMessage *message; - CamelMessageInfo *info; - - /* This is trickier than append_resyncing, because some of - * the messages we are copying may have been copied or - * appended into @source while we were offline, in which case - * if we don't have UIDPLUS, we won't know their real UIDs, - * so we'll have to append them rather than copying. - */ - - realuids = g_ptr_array_new (); - - i = 0; - while (i < uids->len) { - /* Skip past real UIDs */ - for (first = i; i < uids->len; i++) { - uid = uids->pdata[i]; - - if (!isdigit ((unsigned char)*uid)) { - uid = camel_disco_diary_uidmap_lookup (diary, uid); - if (!uid) - break; - } - g_ptr_array_add (realuids, (char *)uid); - - if (delete_originals) - camel_folder_delete_message (source, uid); - } - - /* If we saw any real UIDs, do a COPY */ - if (i != first) { - do_copy (source, realuids, dest, ex); - g_ptr_array_set_size (realuids, 0); - if (i == uids->len || camel_exception_is_set (ex)) - break; - } - - /* Deal with fake UIDs */ - while (i < uids->len && - !isdigit (*(unsigned char *)(uids->pdata[i])) && - !camel_exception_is_set (ex)) { - uid = uids->pdata[i]; - message = camel_folder_get_message (source, uid, NULL); - if (!message) { - /* Message must have been expunged */ - continue; - } - info = camel_folder_get_message_info (source, uid); - g_return_if_fail (info != NULL); - - imap_append_online (dest, message, info, NULL, ex); - camel_folder_free_message_info (source, info); - camel_object_unref (CAMEL_OBJECT (message)); - if (delete_originals) - camel_folder_delete_message (source, uid); - i++; - } - } - - g_ptr_array_free (realuids, FALSE); - - /* FIXME */ - if (transferred_uids) - *transferred_uids = NULL; -} - -static GPtrArray * -imap_search_by_expression (CamelFolder *folder, const char *expression, CamelException *ex) -{ - CamelImapFolder *imap_folder = CAMEL_IMAP_FOLDER (folder); - GPtrArray *matches, *summary; - - /* we could get around this by creating a new search object each time, - but i doubt its worth it since any long operation would lock the - command channel too */ - CAMEL_IMAP_FOLDER_LOCK(folder, search_lock); - - camel_folder_search_set_folder (imap_folder->search, folder); - summary = camel_folder_get_summary(folder); - camel_folder_search_set_summary(imap_folder->search, summary); - matches = camel_folder_search_execute_expression (imap_folder->search, expression, ex); - - CAMEL_IMAP_FOLDER_UNLOCK(folder, search_lock); - - camel_folder_free_summary(folder, summary); - - return matches; -} - -static GPtrArray * -imap_search_by_uids(CamelFolder *folder, const char *expression, GPtrArray *uids, CamelException *ex) -{ - CamelImapFolder *imap_folder = CAMEL_IMAP_FOLDER(folder); - GPtrArray *summary, *matches; - int i; - - /* NOTE: could get away without the search lock by creating a new - search object each time */ - - summary = g_ptr_array_new(); - for (i=0;i<uids->len;i++) { - CamelMessageInfo *info; - - info = camel_folder_get_message_info(folder, uids->pdata[i]); - if (info) - g_ptr_array_add(summary, info); - } - - if (summary->len == 0) - return summary; - - CAMEL_IMAP_FOLDER_LOCK(folder, search_lock); - - camel_folder_search_set_folder(imap_folder->search, folder); - camel_folder_search_set_summary(imap_folder->search, summary); - - matches = camel_folder_search_execute_expression(imap_folder->search, expression, ex); - - CAMEL_IMAP_FOLDER_UNLOCK(folder, search_lock); - - for (i=0;i<summary->len;i++) - camel_folder_free_message_info(folder, summary->pdata[i]); - g_ptr_array_free(summary, TRUE); - - return matches; -} - -static void -imap_search_free (CamelFolder *folder, GPtrArray *uids) -{ - CamelImapFolder *imap_folder = CAMEL_IMAP_FOLDER (folder); - - g_return_if_fail (imap_folder->search); - - CAMEL_IMAP_FOLDER_LOCK(folder, search_lock); - - camel_folder_search_free_result (imap_folder->search, uids); - - CAMEL_IMAP_FOLDER_UNLOCK(folder, search_lock); -} - -static CamelMimeMessage *get_message (CamelImapFolder *imap_folder, - const char *uid, - const char *part_specifier, - CamelMessageContentInfo *ci, - CamelException *ex); - -struct _part_spec_stack { - struct _part_spec_stack *parent; - int part; -}; - -static void -part_spec_push (struct _part_spec_stack **stack, int part) -{ - struct _part_spec_stack *node; - - node = g_new (struct _part_spec_stack, 1); - node->parent = *stack; - node->part = part; - - *stack = node; -} - -static int -part_spec_pop (struct _part_spec_stack **stack) -{ - struct _part_spec_stack *node; - int part; - - g_return_val_if_fail (*stack != NULL, 0); - - node = *stack; - *stack = node->parent; - - part = node->part; - g_free (node); - - return part; -} - -static char * -content_info_get_part_spec (CamelMessageContentInfo *ci) -{ - struct _part_spec_stack *stack = NULL; - CamelMessageContentInfo *node; - char *part_spec, *buf; - size_t len = 1; - int part; - - node = ci; - while (node->parent) { - CamelMessageContentInfo *child; - - /* FIXME: is this only supposed to apply if 'node' is a multipart? */ - if (node->parent->parent && header_content_type_is (node->parent->type, "message", "*")) { - node = node->parent; - continue; - } - - child = node->parent->childs; - for (part = 1; child; part++) { - if (child == node) - break; - - child = child->next; - } - - part_spec_push (&stack, part); - - len += 2; - while ((part = part / 10)) - len++; - - node = node->parent; - } - - buf = part_spec = g_malloc (len); - part_spec[0] = '\0'; - - while (stack) { - part = part_spec_pop (&stack); - buf += sprintf (buf, "%d%s", part, stack ? "." : ""); - } - - return part_spec; -} - -/* Fetch the contents of the MIME part indicated by @ci, which is part - * of message @uid in @folder. - */ -static CamelDataWrapper * -get_content (CamelImapFolder *imap_folder, const char *uid, - CamelMimePart *part, CamelMessageContentInfo *ci, - CamelException *ex) -{ - CamelDataWrapper *content = NULL; - CamelStream *stream; - char *part_spec; - - part_spec = content_info_get_part_spec (ci); - - /* There are three cases: multipart/signed, multipart, message/rfc822, and "other" */ - if (header_content_type_is (ci->type, "multipart", "signed")) { - CamelMultipartSigned *body_mp; - char *spec; - int ret; - - /* Note: because we get the content parts uninterpreted anyway, we could potentially - just use the normalmultipart code, except that multipart/signed wont let you yet! */ - - body_mp = camel_multipart_signed_new (); - /* need to set this so it grabs the boundary and other info about the signed type */ - /* we assume that part->content_type is more accurate/full than ci->type */ - camel_data_wrapper_set_mime_type_field (CAMEL_DATA_WRAPPER (body_mp), part->content_type); - - spec = g_alloca (strlen (part_spec) + 6); - sprintf (spec, part_spec[0] ? "%s.TEXT" : "TEXT", part_spec); - g_free (part_spec); - - stream = camel_imap_folder_fetch_data (imap_folder, uid, spec, FALSE, ex); - if (stream) { - ret = camel_data_wrapper_construct_from_stream (CAMEL_DATA_WRAPPER (body_mp), stream); - camel_object_unref (CAMEL_OBJECT (stream)); - if (ret == -1) { - camel_object_unref ((CamelObject *) body_mp); - return NULL; - } - } - - return (CamelDataWrapper *) body_mp; - } else if (header_content_type_is (ci->type, "multipart", "*")) { - CamelMultipart *body_mp; - char *child_spec; - int speclen, num; - - if (header_content_type_is (ci->type, "multipart", "encrypted")) - body_mp = (CamelMultipart *) camel_multipart_encrypted_new (); - else - body_mp = camel_multipart_new (); - - /* need to set this so it grabs the boundary and other info about the multipart */ - /* we assume that part->content_type is more accurate/full than ci->type */ - camel_data_wrapper_set_mime_type_field (CAMEL_DATA_WRAPPER (body_mp), part->content_type); - - speclen = strlen (part_spec); - child_spec = g_malloc (speclen + 17); /* dot + 10 + dot + MIME + nul */ - memcpy (child_spec, part_spec, speclen); - if (speclen > 0) - child_spec[speclen++] = '.'; - g_free (part_spec); - - ci = ci->childs; - num = 1; - while (ci) { - sprintf (child_spec + speclen, "%d.MIME", num++); - stream = camel_imap_folder_fetch_data (imap_folder, uid, child_spec, FALSE, ex); - if (stream) { - int ret; - - part = camel_mime_part_new (); - ret = camel_data_wrapper_construct_from_stream (CAMEL_DATA_WRAPPER (part), stream); - camel_object_unref (CAMEL_OBJECT (stream)); - if (ret == -1) { - camel_object_unref (CAMEL_OBJECT (part)); - camel_object_unref (CAMEL_OBJECT (body_mp)); - g_free (child_spec); - return NULL; - } - - content = get_content (imap_folder, uid, part, ci, ex); - } - - if (!stream || !content) { - camel_object_unref (CAMEL_OBJECT (body_mp)); - g_free (child_spec); - return NULL; - } - - camel_medium_set_content_object (CAMEL_MEDIUM (part), content); - camel_object_unref (CAMEL_OBJECT (content)); - camel_multipart_add_part (body_mp, part); - camel_object_unref (CAMEL_OBJECT (part)); - - ci = ci->next; - } - - g_free (child_spec); - - return (CamelDataWrapper *) body_mp; - } else if (header_content_type_is (ci->type, "message", "rfc822")) { - content = (CamelDataWrapper *) get_message (imap_folder, uid, part_spec, ci->childs, ex); - g_free (part_spec); - return content; - } else { - content = camel_imap_wrapper_new (imap_folder, ci->type, uid, *part_spec ? part_spec : "1", part); - g_free (part_spec); - return content; - } -} - -static CamelMimeMessage * -get_message (CamelImapFolder *imap_folder, const char *uid, - const char *part_spec, CamelMessageContentInfo *ci, - CamelException *ex) -{ - CamelImapStore *store = CAMEL_IMAP_STORE (CAMEL_FOLDER (imap_folder)->parent_store); - CamelDataWrapper *content; - CamelMimeMessage *msg; - CamelStream *stream; - char *section_text; - int ret; - - section_text = g_strdup_printf ("%s%s%s", part_spec, *part_spec ? "." : "", - store->server_level >= IMAP_LEVEL_IMAP4REV1 ? "HEADER" : "0"); - stream = camel_imap_folder_fetch_data (imap_folder, uid, section_text, FALSE, ex); - g_free (section_text); - if (!stream) - return NULL; - - msg = camel_mime_message_new (); - ret = camel_data_wrapper_construct_from_stream (CAMEL_DATA_WRAPPER (msg), stream); - camel_object_unref (CAMEL_OBJECT (stream)); - if (ret == -1) { - camel_object_unref (CAMEL_OBJECT (msg)); - return NULL; - } - - content = get_content (imap_folder, uid, CAMEL_MIME_PART (msg), ci, ex); - if (!content) { - camel_object_unref (CAMEL_OBJECT (msg)); - return NULL; - } - - camel_medium_set_content_object (CAMEL_MEDIUM (msg), content); - camel_object_unref (CAMEL_OBJECT (content)); - - return msg; -} - -/* FIXME: I pulled this number out of my butt. */ -#define IMAP_SMALL_BODY_SIZE 5120 - -static CamelMimeMessage * -get_message_simple (CamelImapFolder *imap_folder, const char *uid, - CamelStream *stream, CamelException *ex) -{ - CamelMimeMessage *msg; - CamelImapStore *imap_store = - CAMEL_IMAP_STORE (CAMEL_FOLDER (imap_folder)->parent_store); - int ret; - - if (!stream) { - stream = camel_imap_folder_fetch_data (imap_folder, uid, "", - FALSE, ex); - if (!stream) - return NULL; - } - - msg = camel_mime_message_new (); - ret = camel_data_wrapper_construct_from_stream (CAMEL_DATA_WRAPPER (msg), - stream); - camel_object_unref (CAMEL_OBJECT (stream)); - if (ret == -1) { - camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE, - _("Unable to retrieve message: %s"), - g_strerror (errno)); - camel_object_unref (CAMEL_OBJECT (msg)); - return NULL; - } - - /* FIXME, this shouldn't be done this way. */ - camel_medium_set_header (CAMEL_MEDIUM (msg), "X-Evolution-Source", - imap_store->base_url); - return msg; -} - -static CamelMimeMessage * -imap_get_message (CamelFolder *folder, const char *uid, CamelException *ex) -{ - CamelImapFolder *imap_folder = CAMEL_IMAP_FOLDER (folder); - CamelImapStore *store = CAMEL_IMAP_STORE (folder->parent_store); - CamelMessageInfo *mi; - CamelMimeMessage *msg; - CamelStream *stream = NULL; - - /* If the server doesn't support IMAP4rev1, or we already have - * the whole thing cached, fetch it in one piece. - */ - if (store->server_level < IMAP_LEVEL_IMAP4REV1 || - (stream = camel_imap_folder_fetch_data (imap_folder, uid, "", TRUE, NULL))) - return get_message_simple (imap_folder, uid, stream, ex); - - /* If we're not actually connected and it's not in the cache, - * that's as far as we can go. - */ - if (camel_disco_store_check_online (CAMEL_DISCO_STORE (store), ex) == FALSE) - return NULL; - - mi = camel_folder_summary_uid (folder->summary, uid); - if (mi == NULL) { - camel_exception_setv(ex, CAMEL_EXCEPTION_FOLDER_INVALID_UID, - _("Cannot get message: %s\n %s"), uid, _("No such message")); - return NULL; - } - - /* If the message is small, fetch it in one piece. */ - if (mi->size < IMAP_SMALL_BODY_SIZE) { - camel_folder_summary_info_free (folder->summary, mi); - return get_message_simple (imap_folder, uid, NULL, ex); - } - - /* For larger messages, fetch the structure and build a message - * with offline parts. (We check mi->content->type rather than - * mi->content because camel_folder_summary_info_new always creates - * an empty content struct.) - */ - if (!mi->content->type) { - CamelImapResponse *response; - GData *fetch_data = NULL; - char *body, *found_uid; - int i; - - if (camel_disco_store_status (CAMEL_DISCO_STORE (store)) == CAMEL_DISCO_STORE_OFFLINE) { - camel_exception_set (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE, - _("This message is not currently available")); - return NULL; - } - - response = camel_imap_command (store, folder, ex, - "UID FETCH %s BODY", uid); - if (!response) { - camel_folder_summary_info_free (folder->summary, mi); - return NULL; - } - - for (i = 0, body = NULL; i < response->untagged->len; i++) { - fetch_data = parse_fetch_response (imap_folder, response->untagged->pdata[i]); - if (fetch_data) { - found_uid = g_datalist_get_data (&fetch_data, "UID"); - body = g_datalist_get_data (&fetch_data, "BODY"); - if (found_uid && body && !strcmp (found_uid, uid)) - break; - g_datalist_clear (&fetch_data); - fetch_data = NULL; - body = NULL; - } - } - - if (body) - imap_parse_body ((const char **) &body, folder, mi->content); - - if (fetch_data) - g_datalist_clear (&fetch_data); - - camel_imap_response_free (store, response); - - if (!mi->content->type) { - /* FETCH returned OK, but we didn't parse a BODY - * response. Courier will return invalid BODY - * responses for invalidly MIMEd messages, so - * fall back to fetching the entire thing and - * let the mailer's "bad MIME" code handle it. - */ - camel_folder_summary_info_free (folder->summary, mi); - return get_message_simple (imap_folder, uid, NULL, ex); - } - } - - msg = get_message (imap_folder, uid, "", mi->content, ex); - /* FIXME, this shouldn't be done this way. */ - camel_medium_set_header (CAMEL_MEDIUM (msg), "X-Evolution-Source", - store->base_url); - camel_folder_summary_info_free (folder->summary, mi); - - return msg; -} - -static void -imap_cache_message (CamelDiscoFolder *disco_folder, const char *uid, - CamelException *ex) -{ - CamelImapFolder *imap_folder = CAMEL_IMAP_FOLDER (disco_folder); - CamelStream *stream; - - stream = camel_imap_folder_fetch_data (imap_folder, uid, "", FALSE, ex); - if (stream) - camel_object_unref (CAMEL_OBJECT (stream)); -} - -/* We pretend that a FLAGS or RFC822.SIZE response is always exactly - * 20 bytes long, and a BODY[HEADERS] response is always 2000 bytes - * long. Since we know how many of each kind of response we're - * expecting, we can find the total (pretend) amount of server traffic - * to expect and then count off the responses as we read them to update - * the progress bar. - */ -#define IMAP_PRETEND_SIZEOF_FLAGS 20 -#define IMAP_PRETEND_SIZEOF_SIZE 20 -#define IMAP_PRETEND_SIZEOF_HEADERS 2000 - -static char *tm_months[] = { - "Jan", "Feb", "Mar", "Apr", "May", "Jun", - "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" -}; - -static gboolean -decode_time (const unsigned char **in, int *hour, int *min, int *sec) -{ - register const unsigned char *inptr; - int *val, colons = 0; - - *hour = *min = *sec = 0; - - val = hour; - for (inptr = *in; *inptr && !isspace ((int) *inptr); inptr++) { - if (*inptr == ':') { - colons++; - switch (colons) { - case 1: - val = min; - break; - case 2: - val = sec; - break; - default: - return FALSE; - } - } else if (!isdigit ((int) *inptr)) - return FALSE; - else - *val = (*val * 10) + (*inptr - '0'); - } - - *in = inptr; - - return TRUE; -} - -static time_t -decode_internaldate (const unsigned char *in) -{ - const unsigned char *inptr = in; - int hour, min, sec, n; - unsigned char *buf; - struct tm tm; - time_t date; - - memset ((void *) &tm, 0, sizeof (struct tm)); - - tm.tm_mday = strtoul (inptr, (char **) &buf, 10); - if (buf == inptr || *buf != '-') - return (time_t) -1; - - inptr = buf + 1; - if (inptr[3] != '-') - return (time_t) -1; - - for (n = 0; n < 12; n++) { - if (!strncasecmp (inptr, tm_months[n], 3)) - break; - } - - if (n >= 12) - return (time_t) -1; - - tm.tm_mon = n; - - inptr += 4; - - n = strtoul (inptr, (char **) &buf, 10); - if (buf == inptr || *buf != ' ') - return (time_t) -1; - - tm.tm_year = n - 1900; - - inptr = buf + 1; - if (!decode_time (&inptr, &hour, &min, &sec)) - return (time_t) -1; - - tm.tm_hour = hour; - tm.tm_min = min; - tm.tm_sec = sec; - - n = strtol (inptr, NULL, 10); - - date = e_mktime_utc (&tm); - - /* date is now GMT of the time we want, but not offset by the timezone ... */ - - /* this should convert the time to the GMT equiv time */ - date -= ((n / 100) * 60 * 60) + (n % 100) * 60; - - return date; -} - -static void -add_message_from_data (CamelFolder *folder, GPtrArray *messages, - int first, GData *data) -{ - CamelMimeMessage *msg; - CamelStream *stream; - CamelMessageInfo *mi; - const char *idate; - int seq; - - seq = GPOINTER_TO_INT (g_datalist_get_data (&data, "SEQUENCE")); - if (seq < first) - return; - stream = g_datalist_get_data (&data, "BODY_PART_STREAM"); - if (!stream) - return; - - if (seq - first >= messages->len) - g_ptr_array_set_size (messages, seq - first + 1); - - msg = camel_mime_message_new (); - if (camel_data_wrapper_construct_from_stream (CAMEL_DATA_WRAPPER (msg), stream) == -1) { - camel_object_unref (CAMEL_OBJECT (msg)); - return; - } - - mi = camel_folder_summary_info_new_from_message (folder->summary, msg); - camel_object_unref (CAMEL_OBJECT (msg)); - - if ((idate = g_datalist_get_data (&data, "INTERNALDATE"))) - mi->date_received = decode_internaldate (idate); - - if (mi->date_received == -1) - mi->date_received = mi->date_sent; - - messages->pdata[seq - first] = mi; -} - - -#define CAMEL_MESSAGE_INFO_HEADERS "DATE FROM TO CC SUBJECT REFERENCES IN-REPLY-TO MESSAGE-ID MIME-VERSION CONTENT-TYPE" - -/* FIXME: this needs to be kept in sync with camel-mime-utils.c's list - of mailing-list headers and so might be best if this were - auto-generated? */ -#define MAILING_LIST_HEADERS "X-MAILING-LIST X-LOOP LIST-ID LIST-POST MAILING-LIST ORIGINATOR X-LIST SENDER RETURN-PATH X-BEENTHERE" - -static void -imap_update_summary (CamelFolder *folder, int exists, - CamelFolderChangeInfo *changes, - CamelException *ex) -{ - CamelImapStore *store = CAMEL_IMAP_STORE (folder->parent_store); - CamelImapFolder *imap_folder = CAMEL_IMAP_FOLDER (folder); - GPtrArray *fetch_data = NULL, *messages = NULL, *needheaders; - guint32 flags, uidval; - int i, seq, first, size, got; - CamelImapResponseType type; - const char *header_spec; - CamelMessageInfo *mi, *info; - CamelStream *stream; - char *uid, *resp; - GData *data; - - CAMEL_SERVICE_ASSERT_LOCKED (store, connect_lock); - if (store->server_level >= IMAP_LEVEL_IMAP4REV1) - header_spec = "HEADER.FIELDS.NOT (RECEIVED)"; - else - header_spec = "0"; - - /* Figure out if any of the new messages are already cached (which - * may be the case if we're re-syncing after disconnected operation). - * If so, get their UIDs, FLAGS, and SIZEs. If not, get all that - * and ask for the headers too at the same time. - */ - seq = camel_folder_summary_count (folder->summary); - first = seq + 1; - if (seq > 0) { - mi = camel_folder_summary_index (folder->summary, seq - 1); - uidval = strtoul(camel_message_info_uid (mi), NULL, 10); - camel_folder_summary_info_free (folder->summary, mi); - } else - uidval = 0; - - size = (exists - seq) * (IMAP_PRETEND_SIZEOF_FLAGS + IMAP_PRETEND_SIZEOF_SIZE + IMAP_PRETEND_SIZEOF_HEADERS); - got = 0; - if (!camel_imap_command_start (store, folder, ex, - "UID FETCH %d:* (FLAGS RFC822.SIZE INTERNALDATE BODY.PEEK[%s])", - uidval + 1, header_spec)) - return; - camel_operation_start (NULL, _("Fetching summary information for new messages")); - - /* Parse the responses. We can't add a message to the summary - * until we've gotten its headers, and there's no guarantee - * the server will send the responses in a useful order... - */ - fetch_data = g_ptr_array_new (); - messages = g_ptr_array_new (); - while ((type = camel_imap_command_response (store, &resp, ex)) == - CAMEL_IMAP_RESPONSE_UNTAGGED) { - data = parse_fetch_response (imap_folder, resp); - g_free (resp); - if (!data) - continue; - - seq = GPOINTER_TO_INT (g_datalist_get_data (&data, "SEQUENCE")); - if (seq < first) { - g_datalist_clear (&data); - continue; - } - - if (g_datalist_get_data (&data, "FLAGS")) - got += IMAP_PRETEND_SIZEOF_FLAGS; - if (g_datalist_get_data (&data, "RFC822.SIZE")) - got += IMAP_PRETEND_SIZEOF_SIZE; - stream = g_datalist_get_data (&data, "BODY_PART_STREAM"); - if (stream) { - got += IMAP_PRETEND_SIZEOF_HEADERS; - - /* Use the stream now so we don't tie up many - * many fds if we're fetching many many messages. - */ - add_message_from_data (folder, messages, first, data); - g_datalist_set_data (&data, "BODY_PART_STREAM", NULL); - } - - camel_operation_progress (NULL, got * 100 / size); - g_ptr_array_add (fetch_data, data); - } - camel_operation_end (NULL); - - if (type == CAMEL_IMAP_RESPONSE_ERROR) - goto lose; - - /* Free the final tagged response */ - g_free (resp); - - /* Figure out which headers we still need to fetch. */ - needheaders = g_ptr_array_new (); - size = got = 0; - for (i = 0; i < fetch_data->len; i++) { - data = fetch_data->pdata[i]; - if (g_datalist_get_data (&data, "BODY_PART_LEN")) - continue; - - uid = g_datalist_get_data (&data, "UID"); - if (uid) { - g_ptr_array_add (needheaders, uid); - size += IMAP_PRETEND_SIZEOF_HEADERS; - } - } - - /* And fetch them */ - if (needheaders->len) { - char *uidset; - int uid = 0; - - qsort (needheaders->pdata, needheaders->len, - sizeof (void *), uid_compar); - - camel_operation_start (NULL, _("Fetching summary information for new messages")); - - while (uid < needheaders->len) { - uidset = imap_uid_array_to_set (folder->summary, needheaders, uid, UID_SET_LIMIT, &uid); - if (!camel_imap_command_start (store, folder, ex, - "UID FETCH %s BODY.PEEK[%s]", - uidset, header_spec)) { - g_ptr_array_free (needheaders, TRUE); - camel_operation_end (NULL); - g_free (uidset); - goto lose; - } - g_free (uidset); - - while ((type = camel_imap_command_response (store, &resp, ex)) - == CAMEL_IMAP_RESPONSE_UNTAGGED) { - data = parse_fetch_response (imap_folder, resp); - g_free (resp); - if (!data) - continue; - - stream = g_datalist_get_data (&data, "BODY_PART_STREAM"); - if (stream) { - add_message_from_data (folder, messages, first, data); - got += IMAP_PRETEND_SIZEOF_HEADERS; - camel_operation_progress (NULL, got * 100 / size); - } - g_datalist_clear (&data); - } - - if (type == CAMEL_IMAP_RESPONSE_ERROR) { - g_ptr_array_free (needheaders, TRUE); - camel_operation_end (NULL); - goto lose; - } - } - - g_ptr_array_free (needheaders, TRUE); - camel_operation_end (NULL); - } - - /* Now finish up summary entries (fix UIDs, set flags and size) */ - for (i = 0; i < fetch_data->len; i++) { - data = fetch_data->pdata[i]; - - seq = GPOINTER_TO_INT (g_datalist_get_data (&data, "SEQUENCE")); - if (seq >= first + messages->len) { - g_datalist_clear (&data); - continue; - } - - mi = messages->pdata[seq - first]; - if (mi == NULL) { - CamelMessageInfo *pmi = NULL; - int j; - - /* This is a kludge around a bug in Exchange - * 5.5 that sometimes claims multiple messages - * have the same UID. See bug #17694 for - * details. The "solution" is to create a fake - * message-info with the same details as the - * previously valid message. Yes, the user - * will have a clone in his/her message-list, - * but at least we don't crash. - */ - - /* find the previous valid message info */ - for (j = seq - first - 1; j >= 0; j--) { - pmi = messages->pdata[j]; - if (pmi != NULL) - break; - } - - if (pmi == NULL) { - /* Server response is *really* fucked up, - I guess we just pretend it never happened? */ - continue; - } - - mi = camel_message_info_new (); - camel_message_info_dup_to (pmi, mi); - } - - uid = g_datalist_get_data (&data, "UID"); - if (uid) - camel_message_info_set_uid (mi, g_strdup (uid)); - flags = GPOINTER_TO_INT (g_datalist_get_data (&data, "FLAGS")); - if (flags) { - ((CamelImapMessageInfo *)mi)->server_flags = flags; - /* "or" them in with the existing flags that may - * have been set by summary_info_new_from_message. - */ - mi->flags |= flags; - } - size = GPOINTER_TO_INT (g_datalist_get_data (&data, "RFC822.SIZE")); - if (size) - mi->size = size; - - g_datalist_clear (&data); - } - g_ptr_array_free (fetch_data, TRUE); - - /* And add the entries to the summary, etc. */ - for (i = 0; i < messages->len; i++) { - mi = messages->pdata[i]; - if (!mi) { - g_warning ("No information for message %d", i + first); - continue; - } - uid = (char *)camel_message_info_uid(mi); - if (uid[0] == 0) { - g_warning("Server provided no uid: message %d", i + first); - continue; - } - info = camel_folder_summary_uid(folder->summary, uid); - if (info) { - g_warning("Message already present? %s", camel_message_info_uid(mi)); - camel_folder_summary_info_free(folder->summary, info); - camel_folder_summary_info_free(folder->summary, mi); - continue; - } - - camel_folder_summary_add (folder->summary, mi); - camel_folder_change_info_add_uid (changes, camel_message_info_uid (mi)); - - if ((mi->flags & CAMEL_IMAP_MESSAGE_RECENT)) - camel_folder_change_info_recent_uid(changes, camel_message_info_uid (mi)); - } - g_ptr_array_free (messages, TRUE); - - /* Kludge around Microsoft Exchange 5.5 IMAP - See bug #5348 for details */ - if (camel_folder_summary_count (folder->summary) != exists) { - CamelImapStore *imap_store = (CamelImapStore *) folder->parent_store; - CamelImapResponse *response; - - /* forget the currently selected folder */ - if (imap_store->current_folder) { - camel_object_unref (CAMEL_OBJECT (imap_store->current_folder)); - imap_store->current_folder = NULL; - } - - /* now re-select it and process the EXISTS response */ - response = camel_imap_command (imap_store, folder, ex, NULL); - if (response) { - camel_imap_folder_selected (folder, response, NULL); - camel_imap_response_free (imap_store, response); - } - } - - return; - - lose: - if (fetch_data) { - for (i = 0; i < fetch_data->len; i++) { - data = fetch_data->pdata[i]; - g_datalist_clear (&data); - } - g_ptr_array_free (fetch_data, TRUE); - } - if (messages) { - for (i = 0; i < messages->len; i++) { - if (messages->pdata[i]) - camel_folder_summary_info_free (folder->summary, messages->pdata[i]); - } - g_ptr_array_free (messages, TRUE); - } -} - -/* Called with the store's connect_lock locked */ -void -camel_imap_folder_changed (CamelFolder *folder, int exists, - GArray *expunged, CamelException *ex) -{ - CamelImapFolder *imap_folder = CAMEL_IMAP_FOLDER (folder); - CamelFolderChangeInfo *changes; - CamelMessageInfo *info; - int len; - - CAMEL_SERVICE_ASSERT_LOCKED (folder->parent_store, connect_lock); - - changes = camel_folder_change_info_new (); - if (expunged) { - int i, id; - - for (i = 0; i < expunged->len; i++) { - id = g_array_index (expunged, int, i); - info = camel_folder_summary_index (folder->summary, id - 1); - if (info == NULL) { - /* FIXME: danw: does this mean that the summary is corrupt? */ - /* I guess a message that we never retrieved got expunged? */ - continue; - } - - camel_folder_change_info_remove_uid (changes, camel_message_info_uid (info)); - CAMEL_IMAP_FOLDER_LOCK (imap_folder, cache_lock); - camel_imap_message_cache_remove (imap_folder->cache, camel_message_info_uid (info)); - CAMEL_IMAP_FOLDER_UNLOCK (imap_folder, cache_lock); - camel_folder_summary_remove (folder->summary, info); - camel_folder_summary_info_free(folder->summary, info); - } - } - - len = camel_folder_summary_count (folder->summary); - if (exists > len) - imap_update_summary (folder, exists, changes, ex); - - if (camel_folder_change_info_changed (changes)) - camel_object_trigger_event (CAMEL_OBJECT (folder), "folder_changed", changes); - - camel_folder_change_info_free (changes); - camel_folder_summary_save (folder->summary); -} - -static void -imap_thaw (CamelFolder *folder) -{ - CamelImapFolder *imap_folder; - - CAMEL_FOLDER_CLASS (disco_folder_class)->thaw (folder); - if (camel_folder_is_frozen (folder)) - return; - - imap_folder = CAMEL_IMAP_FOLDER (folder); - if (imap_folder->need_refresh) { - imap_folder->need_refresh = FALSE; - imap_refresh_info (folder, NULL); - } -} - - -CamelStream * -camel_imap_folder_fetch_data (CamelImapFolder *imap_folder, const char *uid, - const char *section_text, gboolean cache_only, - CamelException *ex) -{ - CamelFolder *folder = CAMEL_FOLDER (imap_folder); - CamelImapStore *store = CAMEL_IMAP_STORE (folder->parent_store); - CamelImapResponse *response; - CamelStream *stream; - GData *fetch_data; - char *found_uid; - int i; - - /* EXPUNGE responses have to modify the cache, which means - * they have to grab the cache_lock while holding the - * connect_lock. So we grab the connect_lock now, in case - * we're going to need it below, since we can't grab it - * after the cache_lock. - */ - CAMEL_SERVICE_LOCK (store, connect_lock); - - CAMEL_IMAP_FOLDER_LOCK (imap_folder, cache_lock); - stream = camel_imap_message_cache_get (imap_folder->cache, uid, section_text, ex); - if (!stream && (!strcmp (section_text, "HEADER") || !strcmp (section_text, "0"))) { - camel_exception_clear (ex); - stream = camel_imap_message_cache_get (imap_folder->cache, uid, "", ex); - } - - if (stream || cache_only) { - CAMEL_IMAP_FOLDER_UNLOCK (imap_folder, cache_lock); - CAMEL_SERVICE_UNLOCK (store, connect_lock); - return stream; - } - - if (camel_disco_store_status (CAMEL_DISCO_STORE (store)) == CAMEL_DISCO_STORE_OFFLINE) { - camel_exception_set (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE, - _("This message is not currently available")); - CAMEL_IMAP_FOLDER_UNLOCK (imap_folder, cache_lock); - CAMEL_SERVICE_UNLOCK (store, connect_lock); - return NULL; - } - - camel_exception_clear (ex); - if (store->server_level < IMAP_LEVEL_IMAP4REV1 && !*section_text) { - response = camel_imap_command (store, folder, ex, - "UID FETCH %s RFC822.PEEK", - uid); - } else { - response = camel_imap_command (store, folder, ex, - "UID FETCH %s BODY.PEEK[%s]", - uid, section_text); - } - /* We won't need the connect_lock again after this. */ - CAMEL_SERVICE_UNLOCK (store, connect_lock); - - if (!response) { - CAMEL_IMAP_FOLDER_UNLOCK (imap_folder, cache_lock); - return NULL; - } - - for (i = 0; i < response->untagged->len; i++) { - fetch_data = parse_fetch_response (imap_folder, response->untagged->pdata[i]); - found_uid = g_datalist_get_data (&fetch_data, "UID"); - stream = g_datalist_get_data (&fetch_data, "BODY_PART_STREAM"); - if (found_uid && stream && !strcmp (uid, found_uid)) - break; - - g_datalist_clear (&fetch_data); - stream = NULL; - } - camel_imap_response_free (store, response); - CAMEL_IMAP_FOLDER_UNLOCK (imap_folder, cache_lock); - if (!stream) { - camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE, - _("Could not find message body in FETCH response.")); - } else { - camel_object_ref (CAMEL_OBJECT (stream)); - g_datalist_clear (&fetch_data); - } - - return stream; -} - -static GData * -parse_fetch_response (CamelImapFolder *imap_folder, char *response) -{ - GData *data = NULL; - char *start, *part_spec = NULL, *body = NULL, *uid = NULL, *idate = NULL; - gboolean cache_header = TRUE, header = FALSE; - size_t body_len = 0; - - if (*response != '(') { - long seq; - - if (*response != '*' || *(response + 1) != ' ') - return NULL; - seq = strtol (response + 2, &response, 10); - if (seq == 0) - return NULL; - if (strncasecmp (response, " FETCH (", 8) != 0) - return NULL; - response += 7; - - g_datalist_set_data (&data, "SEQUENCE", GINT_TO_POINTER (seq)); - } - - do { - /* Skip the initial '(' or the ' ' between elements */ - response++; - - if (!strncasecmp (response, "FLAGS ", 6)) { - guint32 flags; - - response += 6; - /* FIXME user flags */ - flags = imap_parse_flag_list (&response); - - g_datalist_set_data (&data, "FLAGS", GUINT_TO_POINTER (flags)); - } else if (!strncasecmp (response, "RFC822.SIZE ", 12)) { - unsigned long size; - - response += 12; - size = strtoul (response, &response, 10); - g_datalist_set_data (&data, "RFC822.SIZE", GUINT_TO_POINTER (size)); - } else if (!strncasecmp (response, "BODY[", 5) || - !strncasecmp (response, "RFC822 ", 7)) { - char *p; - - if (*response == 'B') { - response += 5; - - /* HEADER], HEADER.FIELDS (...)], or 0] */ - if (!strncasecmp (response, "HEADER", 6)) { - header = TRUE; - if (!strncasecmp (response + 6, ".FIELDS", 7)) - cache_header = FALSE; - } else if (!strncasecmp (response, "0]", 2)) - header = TRUE; - - p = strchr (response, ']'); - if (!p || *(p + 1) != ' ') - break; - - if (cache_header) - part_spec = g_strndup (response, p - response); - else - part_spec = g_strdup ("HEADER.FIELDS"); - - response = p + 2; - } else { - part_spec = g_strdup (""); - response += 7; - - if (!strncasecmp (response, "HEADER", 6)) - header = TRUE; - } - - body = imap_parse_nstring ((const char **) &response, &body_len); - if (!response) { - g_free (part_spec); - break; - } - - if (!body) - body = g_strdup (""); - g_datalist_set_data_full (&data, "BODY_PART_SPEC", part_spec, g_free); - g_datalist_set_data_full (&data, "BODY_PART_DATA", body, g_free); - g_datalist_set_data (&data, "BODY_PART_LEN", GINT_TO_POINTER (body_len)); - } else if (!strncasecmp (response, "BODY ", 5) || - !strncasecmp (response, "BODYSTRUCTURE ", 14)) { - response = strchr (response, ' ') + 1; - start = response; - imap_skip_list ((const char **) &response); - g_datalist_set_data_full (&data, "BODY", g_strndup (start, response - start), g_free); - } else if (!strncasecmp (response, "UID ", 4)) { - int len; - - len = strcspn (response + 4, " )"); - uid = g_strndup (response + 4, len); - g_datalist_set_data_full (&data, "UID", uid, g_free); - response += 4 + len; - } else if (!strncasecmp (response, "INTERNALDATE ", 13)) { - int len; - - response += 13; - if (*response == '"') { - response++; - len = strcspn (response, "\""); - idate = g_strndup (response, len); - g_datalist_set_data_full (&data, "INTERNALDATE", idate, g_free); - response += len + 1; - } - } else { - g_warning ("Unexpected FETCH response from server: (%s", response); - break; - } - } while (response && *response != ')'); - - if (!response || *response != ')') { - g_datalist_clear (&data); - return NULL; - } - - if (uid && body) { - CamelStream *stream; - - if (header && !cache_header) { - stream = camel_stream_mem_new_with_buffer (body, body_len); - } else { - CAMEL_IMAP_FOLDER_LOCK (imap_folder, cache_lock); - stream = camel_imap_message_cache_insert (imap_folder->cache, - uid, part_spec, - body, body_len, NULL); - CAMEL_IMAP_FOLDER_UNLOCK (imap_folder, cache_lock); - } - - if (stream) - g_datalist_set_data_full (&data, "BODY_PART_STREAM", stream, - (GDestroyNotify) camel_object_unref); - } - - return data; -} - |