1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /* camel-imap-folder.c: class for an imap folder */
6 * Dan Winship <danw@ximian.com>
7 * Jeffrey Stedfast <fejj@ximian.com>
9 * Copyright (C) 2000, 2001 Ximian, Inc.
11 * This program is free software; you can redistribute it and/or
12 * modify it under the terms of version 2 of the GNU 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
18 * GNU Lesser General Public License for more details.
20 * You should have received a copy of the GNU Lesser General Public License
21 * along with this program; if not, write to the Free Software
22 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
35 #include <sys/types.h>
37 #include <glib/gi18n-lib.h>
39 #include <libedataserver/e-data-server-util.h>
40 #include <libedataserver/e-time-utils.h>
42 #include "camel-data-wrapper.h"
43 #include "camel-debug.h"
44 #include "camel-disco-diary.h"
45 #include "camel-exception.h"
46 #include "camel-file-utils.h"
47 #include "camel-mime-filter-crlf.h"
48 #include "camel-mime-filter-from.h"
49 #include "camel-mime-message.h"
50 #include "camel-mime-utils.h"
51 #include "camel-multipart-encrypted.h"
52 #include "camel-multipart-signed.h"
53 #include "camel-multipart.h"
54 #include "camel-operation.h"
55 #include "camel-private.h"
56 #include "camel-session.h"
57 #include "camel-store-summary.h"
58 #include "camel-stream-buffer.h"
59 #include "camel-stream-filter.h"
60 #include "camel-stream-mem.h"
61 #include "camel-stream.h"
62 #include "camel-string-utils.h"
64 #include "camel-imap-command.h"
65 #include "camel-imap-folder.h"
66 #include "camel-imap-message-cache.h"
67 #include "camel-imap-private.h"
68 #include "camel-imap-search.h"
69 #include "camel-imap-store.h"
70 #include "camel-imap-summary.h"
71 #include "camel-imap-utils.h"
72 #include "camel-imap-wrapper.h"
76 /* set to -1 for infinite size (suggested max command-line length is
77 * 1000 octets (see rfc2683), so we should keep the uid-set length to
78 * something under that so that our command-lines don't exceed 1000
80 #define UID_SET_LIMIT (768)
83 #define CF_CLASS(o) (CAMEL_FOLDER_CLASS (CAMEL_OBJECT_GET_CLASS(o)))
84 static CamelDiscoFolderClass *disco_folder_class = NULL;
86 static void imap_finalize (CamelObject *object);
87 static int imap_getv(CamelObject *object, CamelException *ex, CamelArgGetV *args);
89 static void imap_rescan (CamelFolder *folder, int exists, CamelException *ex);
90 static void imap_refresh_info (CamelFolder *folder, CamelException *ex);
91 static void imap_sync_online (CamelFolder *folder, CamelException *ex);
92 static void imap_sync_offline (CamelFolder *folder, CamelException *ex);
93 static void imap_expunge_uids_online (CamelFolder *folder, GPtrArray *uids, CamelException *ex);
94 static void imap_expunge_uids_offline (CamelFolder *folder, GPtrArray *uids, CamelException *ex);
95 static void imap_expunge_uids_resyncing (CamelFolder *folder, GPtrArray *uids, CamelException *ex);
96 static void imap_cache_message (CamelDiscoFolder *disco_folder, const char *uid, CamelException *ex);
97 static void imap_rename (CamelFolder *folder, const char *new);
99 /* message manipulation */
100 static CamelMimeMessage *imap_get_message (CamelFolder *folder, const gchar *uid,
102 static void imap_append_online (CamelFolder *folder, CamelMimeMessage *message,
103 const CamelMessageInfo *info, char **appended_uid,
105 static void imap_append_offline (CamelFolder *folder, CamelMimeMessage *message,
106 const CamelMessageInfo *info, char **appended_uid,
108 static void imap_append_resyncing (CamelFolder *folder, CamelMimeMessage *message,
109 const CamelMessageInfo *info, char **appended_uid,
112 static void imap_transfer_online (CamelFolder *source, GPtrArray *uids,
113 CamelFolder *dest, GPtrArray **transferred_uids,
114 gboolean delete_originals,
116 static void imap_transfer_offline (CamelFolder *source, GPtrArray *uids,
117 CamelFolder *dest, GPtrArray **transferred_uids,
118 gboolean delete_originals,
120 static void imap_transfer_resyncing (CamelFolder *source, GPtrArray *uids,
121 CamelFolder *dest, GPtrArray **transferred_uids,
122 gboolean delete_originals,
126 static GPtrArray *imap_search_by_expression (CamelFolder *folder, const char *expression, CamelException *ex);
127 static GPtrArray *imap_search_by_uids (CamelFolder *folder, const char *expression, GPtrArray *uids, CamelException *ex);
128 static void imap_search_free (CamelFolder *folder, GPtrArray *uids);
130 static void imap_thaw (CamelFolder *folder);
132 static CamelObjectClass *parent_class;
134 static GData *parse_fetch_response (CamelImapFolder *imap_folder, char *msg_att);
137 /* The strtok() in Microsoft's C library is MT-safe (but still uses
138 * only one buffer pointer per thread, but for the use of strtok_r()
139 * here that's enough).
141 #define strtok_r(s,sep,lasts) (*(lasts)=strtok((s),(sep)))
145 camel_imap_folder_class_init (CamelImapFolderClass *camel_imap_folder_class)
147 CamelFolderClass *camel_folder_class = CAMEL_FOLDER_CLASS (camel_imap_folder_class);
148 CamelDiscoFolderClass *camel_disco_folder_class = CAMEL_DISCO_FOLDER_CLASS (camel_imap_folder_class);
150 disco_folder_class = CAMEL_DISCO_FOLDER_CLASS (camel_type_get_global_classfuncs (camel_disco_folder_get_type ()));
152 /* virtual method overload */
153 ((CamelObjectClass *)camel_imap_folder_class)->getv = imap_getv;
155 camel_folder_class->get_message = imap_get_message;
156 camel_folder_class->rename = imap_rename;
157 camel_folder_class->search_by_expression = imap_search_by_expression;
158 camel_folder_class->search_by_uids = imap_search_by_uids;
159 camel_folder_class->search_free = imap_search_free;
160 camel_folder_class->thaw = imap_thaw;
162 camel_disco_folder_class->refresh_info_online = imap_refresh_info;
163 camel_disco_folder_class->sync_online = imap_sync_online;
164 camel_disco_folder_class->sync_offline = imap_sync_offline;
165 /* We don't sync flags at resync time: the online code will
166 * deal with it eventually.
168 camel_disco_folder_class->sync_resyncing = imap_sync_offline;
169 camel_disco_folder_class->expunge_uids_online = imap_expunge_uids_online;
170 camel_disco_folder_class->expunge_uids_offline = imap_expunge_uids_offline;
171 camel_disco_folder_class->expunge_uids_resyncing = imap_expunge_uids_resyncing;
172 camel_disco_folder_class->append_online = imap_append_online;
173 camel_disco_folder_class->append_offline = imap_append_offline;
174 camel_disco_folder_class->append_resyncing = imap_append_resyncing;
175 camel_disco_folder_class->transfer_online = imap_transfer_online;
176 camel_disco_folder_class->transfer_offline = imap_transfer_offline;
177 camel_disco_folder_class->transfer_resyncing = imap_transfer_resyncing;
178 camel_disco_folder_class->cache_message = imap_cache_message;
182 camel_imap_folder_init (gpointer object, gpointer klass)
184 CamelImapFolder *imap_folder = CAMEL_IMAP_FOLDER (object);
185 CamelFolder *folder = CAMEL_FOLDER (object);
187 folder->permanent_flags = CAMEL_MESSAGE_ANSWERED | CAMEL_MESSAGE_DELETED |
188 CAMEL_MESSAGE_DRAFT | CAMEL_MESSAGE_FLAGGED | CAMEL_MESSAGE_SEEN;
190 folder->folder_flags |= (CAMEL_FOLDER_HAS_SUMMARY_CAPABILITY |
191 CAMEL_FOLDER_HAS_SEARCH_CAPABILITY);
193 imap_folder->priv = g_malloc0(sizeof(*imap_folder->priv));
194 #ifdef ENABLE_THREADS
195 g_static_mutex_init(&imap_folder->priv->search_lock);
196 g_static_rec_mutex_init(&imap_folder->priv->cache_lock);
199 imap_folder->need_rescan = TRUE;
203 camel_imap_folder_get_type (void)
205 static CamelType camel_imap_folder_type = CAMEL_INVALID_TYPE;
207 if (camel_imap_folder_type == CAMEL_INVALID_TYPE) {
208 parent_class = camel_disco_folder_get_type();
209 camel_imap_folder_type =
210 camel_type_register (parent_class, "CamelImapFolder",
211 sizeof (CamelImapFolder),
212 sizeof (CamelImapFolderClass),
213 (CamelObjectClassInitFunc) camel_imap_folder_class_init,
215 (CamelObjectInitFunc) camel_imap_folder_init,
216 (CamelObjectFinalizeFunc) imap_finalize);
219 return camel_imap_folder_type;
223 camel_imap_folder_new (CamelStore *parent, const char *folder_name,
224 const char *folder_dir, CamelException *ex)
226 CamelImapStore *imap_store = CAMEL_IMAP_STORE (parent);
228 CamelImapFolder *imap_folder;
229 const char *short_name;
230 char *summary_file, *state_file;
232 if (g_mkdir_with_parents (folder_dir, S_IRWXU) != 0) {
233 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
234 _("Could not create directory %s: %s"),
235 folder_dir, g_strerror (errno));
239 folder = CAMEL_FOLDER (camel_object_new (camel_imap_folder_get_type ()));
240 short_name = strrchr (folder_name, '/');
244 short_name = folder_name;
245 camel_folder_construct (folder, parent, folder_name, short_name);
247 summary_file = g_strdup_printf ("%s/summary", folder_dir);
248 folder->summary = camel_imap_summary_new (folder, summary_file);
249 g_free (summary_file);
250 if (!folder->summary) {
251 camel_object_unref (CAMEL_OBJECT (folder));
252 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
253 _("Could not load summary for %s"),
258 /* set/load persistent state */
259 state_file = g_strdup_printf ("%s/cmeta", folder_dir);
260 camel_object_set(folder, NULL, CAMEL_OBJECT_STATE_FILE, state_file, NULL);
262 camel_object_state_read(folder);
264 imap_folder = CAMEL_IMAP_FOLDER (folder);
265 imap_folder->cache = camel_imap_message_cache_new (folder_dir, folder->summary, ex);
266 if (!imap_folder->cache) {
267 camel_object_unref (CAMEL_OBJECT (folder));
271 if (!g_ascii_strcasecmp (folder_name, "INBOX")) {
272 if ((imap_store->parameters & IMAP_PARAM_FILTER_INBOX))
273 folder->folder_flags |= CAMEL_FOLDER_FILTER_RECENT;
274 if ((imap_store->parameters & IMAP_PARAM_FILTER_JUNK))
275 folder->folder_flags |= CAMEL_FOLDER_FILTER_JUNK;
277 if ((imap_store->parameters & (IMAP_PARAM_FILTER_JUNK|IMAP_PARAM_FILTER_JUNK_INBOX)) == (IMAP_PARAM_FILTER_JUNK))
278 folder->folder_flags |= CAMEL_FOLDER_FILTER_JUNK;
281 imap_folder->search = camel_imap_search_new(folder_dir);
286 /* Called with the store's connect_lock locked */
288 camel_imap_folder_selected (CamelFolder *folder, CamelImapResponse *response,
291 CamelImapFolder *imap_folder = CAMEL_IMAP_FOLDER (folder);
292 CamelImapSummary *imap_summary = CAMEL_IMAP_SUMMARY (folder->summary);
293 unsigned long exists = 0, validity = 0, val, uid;
294 CamelMessageInfo *info;
295 guint32 perm_flags = 0;
300 count = camel_folder_summary_count (folder->summary);
302 for (i = 0; i < response->untagged->len; i++) {
303 resp = response->untagged->pdata[i] + 2;
304 if (!g_ascii_strncasecmp (resp, "FLAGS ", 6) && !perm_flags) {
306 folder->permanent_flags = imap_parse_flag_list (&resp);
307 } else if (!g_ascii_strncasecmp (resp, "OK [PERMANENTFLAGS ", 19)) {
310 /* workaround for broken IMAP servers that send "* OK [PERMANENTFLAGS ()] Permanent flags"
311 * even tho they do allow storing flags. *Sigh* So many fucking broken IMAP servers out there. */
312 if ((perm_flags = imap_parse_flag_list (&resp)) != 0)
313 folder->permanent_flags = perm_flags;
314 } else if (!g_ascii_strncasecmp (resp, "OK [UIDVALIDITY ", 16)) {
315 validity = strtoul (resp + 16, NULL, 10);
316 } else if (isdigit ((unsigned char)*resp)) {
317 unsigned long num = strtoul (resp, &resp, 10);
319 if (!g_ascii_strncasecmp (resp, " EXISTS", 7)) {
321 /* Remove from the response so nothing
322 * else tries to interpret it.
324 g_free (response->untagged->pdata[i]);
325 g_ptr_array_remove_index (response->untagged, i--);
330 if (camel_strstrcase (response->status, "OK [READ-ONLY]"))
331 imap_folder->read_only = TRUE;
333 if (camel_disco_store_status (CAMEL_DISCO_STORE (folder->parent_store)) == CAMEL_DISCO_STORE_RESYNCING) {
334 if (validity != imap_summary->validity) {
335 camel_exception_setv (ex, CAMEL_EXCEPTION_FOLDER_SUMMARY_INVALID,
336 _("Folder was destroyed and recreated on server."));
340 /* FIXME: find missing UIDs ? */
344 if (!imap_summary->validity)
345 imap_summary->validity = validity;
346 else if (validity != imap_summary->validity) {
347 imap_summary->validity = validity;
348 camel_folder_summary_clear (folder->summary);
349 CAMEL_IMAP_FOLDER_REC_LOCK (imap_folder, cache_lock);
350 camel_imap_message_cache_clear (imap_folder->cache);
351 CAMEL_IMAP_FOLDER_REC_UNLOCK (imap_folder, cache_lock);
352 imap_folder->need_rescan = FALSE;
353 camel_imap_folder_changed (folder, exists, NULL, ex);
357 /* If we've lost messages, we have to rescan everything */
359 imap_folder->need_rescan = TRUE;
360 else if (count != 0 && !imap_folder->need_rescan) {
361 CamelImapStore *store = CAMEL_IMAP_STORE (folder->parent_store);
363 /* Similarly, if the UID of the highest message we
364 * know about has changed, then that indicates that
365 * messages have been both added and removed, so we
366 * have to rescan to find the removed ones. (We pass
367 * NULL for the folder since we know that this folder
368 * is selected, and we don't want camel_imap_command
369 * to worry about it.)
371 response = camel_imap_command (store, NULL, ex, "FETCH %d UID", count);
375 for (i = 0; i < response->untagged->len; i++) {
376 resp = response->untagged->pdata[i];
377 val = strtoul (resp + 2, &resp, 10);
380 if (!g_ascii_strcasecmp (resp, " EXISTS")) {
385 if (uid != 0 || val != count || g_ascii_strncasecmp (resp, " FETCH (", 8) != 0)
388 fetch_data = parse_fetch_response (imap_folder, resp + 7);
389 uid = strtoul (g_datalist_get_data (&fetch_data, "UID"), NULL, 10);
390 g_datalist_clear (&fetch_data);
392 camel_imap_response_free_without_processing (store, response);
394 info = camel_folder_summary_index (folder->summary, count - 1);
395 val = strtoul (camel_message_info_uid (info), NULL, 10);
396 camel_message_info_free(info);
397 if (uid == 0 || uid != val)
398 imap_folder->need_rescan = TRUE;
401 /* Now rescan if we need to */
402 if (imap_folder->need_rescan) {
403 imap_rescan (folder, exists, ex);
407 /* If we don't need to rescan completely, but new messages
408 * have been added, find out about them.
411 camel_imap_folder_changed (folder, exists, NULL, ex);
413 /* And we're done. */
417 imap_finalize (CamelObject *object)
419 CamelImapFolder *imap_folder = CAMEL_IMAP_FOLDER (object);
421 if (imap_folder->search)
422 camel_object_unref (CAMEL_OBJECT (imap_folder->search));
423 if (imap_folder->cache)
424 camel_object_unref (CAMEL_OBJECT (imap_folder->cache));
426 #ifdef ENABLE_THREADS
427 g_static_mutex_free(&imap_folder->priv->search_lock);
428 g_static_rec_mutex_free(&imap_folder->priv->cache_lock);
430 g_free(imap_folder->priv);
434 imap_getv(CamelObject *object, CamelException *ex, CamelArgGetV *args)
436 CamelFolder *folder = (CamelFolder *)object;
440 for (i=0;i<args->argc;i++) {
441 CamelArgGet *arg = &args->argv[i];
445 switch (tag & CAMEL_ARG_TAG) {
446 /* CamelObject args */
447 case CAMEL_OBJECT_ARG_DESCRIPTION:
448 if (folder->description == NULL) {
449 CamelURL *uri = ((CamelService *)folder->parent_store)->url;
451 /* what if the full name doesn't incclude /'s? does it matter? */
452 folder->description = g_strdup_printf("%s@%s:%s", uri->user, uri->host, folder->full_name);
454 *arg->ca_str = folder->description;
461 arg->tag = (tag & CAMEL_ARG_TYPE) | CAMEL_ARG_IGNORE;
465 return ((CamelObjectClass *)parent_class)->getv(object, ex, args);
471 imap_rename (CamelFolder *folder, const char *new)
473 CamelImapFolder *imap_folder = (CamelImapFolder *)folder;
474 CamelImapStore *imap_store = (CamelImapStore *)folder->parent_store;
475 char *folder_dir, *summary_path, *state_file;
478 folders = g_strconcat (imap_store->storage_path, "/folders", NULL);
479 folder_dir = imap_path_to_physical (folders, new);
481 summary_path = g_strdup_printf("%s/summary", folder_dir);
483 CAMEL_IMAP_FOLDER_REC_LOCK (folder, cache_lock);
484 camel_imap_message_cache_set_path(imap_folder->cache, folder_dir);
485 CAMEL_IMAP_FOLDER_REC_UNLOCK (folder, cache_lock);
487 camel_folder_summary_set_filename(folder->summary, summary_path);
489 state_file = g_strdup_printf ("%s/cmeta", folder_dir);
490 camel_object_set(folder, NULL, CAMEL_OBJECT_STATE_FILE, state_file, NULL);
493 g_free(summary_path);
496 ((CamelFolderClass *)disco_folder_class)->rename(folder, new);
500 imap_refresh_info (CamelFolder *folder, CamelException *ex)
502 CamelImapStore *imap_store = CAMEL_IMAP_STORE (folder->parent_store);
503 CamelImapFolder *imap_folder = CAMEL_IMAP_FOLDER (folder);
504 CamelImapResponse *response;
507 if (camel_disco_store_status (CAMEL_DISCO_STORE (imap_store)) == CAMEL_DISCO_STORE_OFFLINE)
510 if (camel_folder_is_frozen (folder)) {
511 imap_folder->need_refresh = TRUE;
515 /* If the folder isn't selected, select it (which will force
516 * a rescan if one is needed).
517 * Also, if this is the INBOX, some servers (cryus) wont tell
518 * us with a NOOP of new messages, so force a reselect which
520 CAMEL_SERVICE_REC_LOCK (imap_store, connect_lock);
522 if (!camel_imap_store_connected(imap_store, ex))
525 if (imap_store->current_folder != folder
526 || g_ascii_strcasecmp(folder->full_name, "INBOX") == 0) {
527 response = camel_imap_command (imap_store, folder, ex, NULL);
529 camel_imap_folder_selected (folder, response, ex);
530 camel_imap_response_free (imap_store, response);
532 } else if (imap_folder->need_rescan) {
533 /* Otherwise, if we need a rescan, do it, and if not, just do
534 * a NOOP to give the server a chance to tell us about new
537 imap_rescan (folder, camel_folder_summary_count (folder->summary), ex);
540 /* on some servers need to CHECKpoint INBOX to recieve new messages?? */
541 /* rfc2060 suggests this, but havent seen a server that requires it */
542 if (g_ascii_strcasecmp(folder->full_name, "INBOX") == 0) {
543 response = camel_imap_command (imap_store, folder, ex, "CHECK");
544 camel_imap_response_free (imap_store, response);
547 response = camel_imap_command (imap_store, folder, ex, "NOOP");
548 camel_imap_response_free (imap_store, response);
551 si = camel_store_summary_path((CamelStoreSummary *)((CamelImapStore *)folder->parent_store)->summary, folder->full_name);
553 guint32 unread, total;
555 camel_object_get(folder, NULL, CAMEL_FOLDER_TOTAL, &total, CAMEL_FOLDER_UNREAD, &unread, NULL);
556 if (si->total != total
557 || si->unread != unread){
560 camel_store_summary_touch((CamelStoreSummary *)((CamelImapStore *)folder->parent_store)->summary);
562 camel_store_summary_info_free((CamelStoreSummary *)((CamelImapStore *)folder->parent_store)->summary, si);
565 CAMEL_SERVICE_REC_UNLOCK (imap_store, connect_lock);
567 camel_folder_summary_save(folder->summary);
568 camel_store_summary_save((CamelStoreSummary *)((CamelImapStore *)folder->parent_store)->summary);
572 flags_to_label(CamelFolder *folder, CamelImapMessageInfo *mi)
574 /* We snoop the server flag setting, and map it to
575 the label 'user tag'. We also clean it up at this
576 point, there can only be 1 label set at a time */
577 if (folder->permanent_flags & CAMEL_IMAP_MESSAGE_LABEL_MASK) {
578 const char *label = NULL;
581 if (mi->info.flags & CAMEL_IMAP_MESSAGE_LABEL1) {
582 mask = CAMEL_IMAP_MESSAGE_LABEL1;
584 } else if (mi->info.flags & CAMEL_IMAP_MESSAGE_LABEL2) {
585 mask = CAMEL_IMAP_MESSAGE_LABEL2;
587 } else if (mi->info.flags & CAMEL_IMAP_MESSAGE_LABEL3) {
588 mask = CAMEL_IMAP_MESSAGE_LABEL3;
590 } else if (mi->info.flags & CAMEL_IMAP_MESSAGE_LABEL4) {
591 mask = CAMEL_IMAP_MESSAGE_LABEL4;
593 } else if (mi->info.flags & CAMEL_IMAP_MESSAGE_LABEL5) {
594 mask = CAMEL_IMAP_MESSAGE_LABEL5;
598 mi->info.flags = (mi->info.flags & ~CAMEL_IMAP_MESSAGE_LABEL_MASK) | mask;
599 camel_tag_set(&mi->info.user_tags, "label", label);
603 /* Called with the store's connect_lock locked */
605 imap_rescan (CamelFolder *folder, int exists, CamelException *ex)
607 CamelImapFolder *imap_folder = CAMEL_IMAP_FOLDER (folder);
608 CamelImapStore *store = CAMEL_IMAP_STORE (folder->parent_store);
614 CamelImapResponseType type;
615 int i, seq, summary_len, summary_got;
616 CamelMessageInfo *info;
617 CamelImapMessageInfo *iinfo;
620 CamelFolderChangeInfo *changes = NULL;
622 imap_folder->need_rescan = FALSE;
624 summary_len = camel_folder_summary_count (folder->summary);
625 if (summary_len == 0) {
627 camel_imap_folder_changed (folder, exists, NULL, ex);
631 /* Check UIDs and flags of all messages we already know of. */
632 camel_operation_start (NULL, _("Scanning for changed messages in %s"), folder->name);
633 info = camel_folder_summary_index (folder->summary, summary_len - 1);
634 ok = camel_imap_command_start (store, folder, ex,
635 "UID FETCH 1:%s (FLAGS)",
636 camel_message_info_uid (info));
637 camel_message_info_free(info);
639 camel_operation_end (NULL);
643 new = g_malloc0 (summary_len * sizeof (*new));
645 while ((type = camel_imap_command_response (store, &resp, ex)) == CAMEL_IMAP_RESPONSE_UNTAGGED) {
650 data = parse_fetch_response (imap_folder, resp);
655 seq = GPOINTER_TO_INT (g_datalist_get_data (&data, "SEQUENCE"));
656 uid = g_datalist_get_data (&data, "UID");
657 flags = GPOINTER_TO_UINT (g_datalist_get_data (&data, "FLAGS"));
659 if (!uid || !seq || seq > summary_len || seq < 0) {
660 g_datalist_clear (&data);
664 camel_operation_progress (NULL, ++summary_got * 100 / summary_len);
665 new[seq - 1].uid = g_strdup (uid);
666 new[seq - 1].flags = flags;
667 g_datalist_clear (&data);
670 camel_operation_end (NULL);
671 if (type == CAMEL_IMAP_RESPONSE_ERROR) {
672 for (i = 0; i < summary_len && new[i].uid; i++)
678 /* Free the final tagged response */
681 /* If we find a UID in the summary that doesn't correspond to
682 * the UID in the folder, then either: (a) it's a real UID,
683 * but the message was deleted on the server, or (b) it's a
684 * fake UID, and needs to be removed from the summary in order
685 * to sync up with the server. So either way, we remove it
688 removed = g_array_new (FALSE, FALSE, sizeof (int));
689 for (i = 0; i < summary_len && new[i].uid; i++) {
690 info = camel_folder_summary_index (folder->summary, i);
691 iinfo = (CamelImapMessageInfo *)info;
693 if (strcmp (camel_message_info_uid (info), new[i].uid) != 0) {
694 camel_message_info_free(info);
696 g_array_append_val (removed, seq);
702 /* Update summary flags */
703 if (new[i].flags != iinfo->server_flags) {
704 guint32 server_set, server_cleared;
706 server_set = new[i].flags & ~iinfo->server_flags;
707 server_cleared = iinfo->server_flags & ~new[i].flags;
709 iinfo->info.flags = (iinfo->info.flags | server_set) & ~server_cleared;
710 iinfo->server_flags = new[i].flags;
713 changes = camel_folder_change_info_new();
714 camel_folder_change_info_change_uid(changes, new[i].uid);
715 flags_to_label(folder, (CamelImapMessageInfo *)info);
718 camel_message_info_free(info);
723 camel_object_trigger_event(CAMEL_OBJECT (folder), "folder_changed", changes);
724 camel_folder_change_info_free(changes);
729 /* Free remaining memory. */
730 while (i < summary_len && new[i].uid)
731 g_free (new[i++].uid);
734 /* Remove any leftover cached summary messages. (Yes, we
735 * repeatedly add the same number to the removed array.
739 for (i = seq; i <= summary_len; i++)
740 g_array_append_val (removed, seq);
742 /* And finally update the summary. */
743 camel_imap_folder_changed (folder, exists, removed, ex);
744 g_array_free (removed, TRUE);
747 /* the max number of chars that an unsigned 32-bit int can be is 10 chars plus 1 for a possible : */
748 #define UID_SET_FULL(setlen, maxlen) (maxlen > 0 ? setlen + 11 >= maxlen : FALSE)
750 /* Find all messages in @folder with flags matching @flags and @mask.
751 * If no messages match, returns %NULL. Otherwise, returns an array of
752 * CamelMessageInfo and sets *@set to a message set corresponding the
753 * UIDs of the matched messages (up to @UID_SET_LIMIT bytes). The
754 * caller must free the infos, the array, and the set string.
757 get_matching (CamelFolder *folder, guint32 flags, guint32 mask, char **set)
760 CamelImapMessageInfo *info;
764 matches = g_ptr_array_new ();
765 gset = g_string_new ("");
766 max = camel_folder_summary_count (folder->summary);
768 for (i = 0; i < max && !UID_SET_FULL (gset->len, UID_SET_LIMIT); i++) {
769 info = (CamelImapMessageInfo *)camel_folder_summary_index (folder->summary, i);
772 if ((info->info.flags & mask) != flags) {
773 camel_message_info_free((CamelMessageInfo *)info);
775 if (range != i - 1) {
776 info = matches->pdata[matches->len - 1];
777 g_string_append_printf (gset, ":%s", camel_message_info_uid (info));
784 g_ptr_array_add (matches, info);
789 g_string_append_c (gset, ',');
790 g_string_append_printf (gset, "%s", camel_message_info_uid (info));
793 if (range != -1 && range != max - 1) {
794 info = matches->pdata[matches->len - 1];
795 g_string_append_printf (gset, ":%s", camel_message_info_uid (info));
800 g_string_free (gset, FALSE);
804 g_string_free (gset, TRUE);
805 g_ptr_array_free (matches, TRUE);
811 imap_sync_offline (CamelFolder *folder, CamelException *ex)
813 camel_folder_summary_save (folder->summary);
814 camel_store_summary_save((CamelStoreSummary *)((CamelImapStore *)folder->parent_store)->summary);
818 imap_sync_online (CamelFolder *folder, CamelException *ex)
820 CamelImapStore *store = CAMEL_IMAP_STORE (folder->parent_store);
821 CamelImapResponse *response = NULL;
822 CamelImapMessageInfo *info;
823 CamelException local_ex;
825 char *set, *flaglist;
829 if (folder->permanent_flags == 0) {
830 imap_sync_offline (folder, ex);
834 camel_exception_init (&local_ex);
835 CAMEL_SERVICE_REC_LOCK (store, connect_lock);
837 /* Find a message with changed flags, find all of the other
838 * messages like it, sync them as a group, mark them as
839 * updated, and continue.
841 max = camel_folder_summary_count (folder->summary);
842 for (i = 0; i < max; i++) {
843 if (!(info = (CamelImapMessageInfo *)camel_folder_summary_index (folder->summary, i)))
846 if (!(info->info.flags & CAMEL_MESSAGE_FOLDER_FLAGGED)) {
847 camel_message_info_free((CamelMessageInfo *)info);
851 /* Note: Cyrus is broken and will not accept an
852 empty-set of flags so... if this is true then we
853 want to unset the previously set flags.*/
854 unset = !(info->info.flags & folder->permanent_flags);
856 /* Note: get_matching() uses UID_SET_LIMIT to limit
857 the size of the uid-set string. We don't have to
858 loop here to flush all the matching uids because
859 they will be scooped up later by our parent loop (I
861 matches = get_matching (folder, info->info.flags & (folder->permanent_flags | CAMEL_MESSAGE_FOLDER_FLAGGED),
862 folder->permanent_flags | CAMEL_MESSAGE_FOLDER_FLAGGED, &set);
863 camel_message_info_free(info);
867 /* Make sure we're connected before issuing commands */
868 if (!camel_imap_store_connected(store, ex)) {
873 /* FIXME: since we don't know the previously set flags,
874 if unset is TRUE then just unset all the flags? */
875 flaglist = imap_create_flag_list (unset ? folder->permanent_flags : info->info.flags & folder->permanent_flags);
877 /* Note: to `unset' flags, use -FLAGS.SILENT (<flag list>) */
878 response = camel_imap_command (store, folder, &local_ex,
879 "UID STORE %s %sFLAGS.SILENT %s",
880 set, unset ? "-" : "", flaglist);
885 camel_imap_response_free (store, response);
887 if (!camel_exception_is_set (&local_ex)) {
888 for (j = 0; j < matches->len; j++) {
889 info = matches->pdata[j];
890 info->info.flags &= ~CAMEL_MESSAGE_FOLDER_FLAGGED;
891 ((CamelImapMessageInfo *) info)->server_flags = info->info.flags & CAMEL_IMAP_SERVER_FLAGS;
893 camel_folder_summary_touch (folder->summary);
896 for (j = 0; j < matches->len; j++) {
897 info = matches->pdata[j];
898 camel_message_info_free(&info->info);
900 g_ptr_array_free (matches, TRUE);
902 /* We unlock here so that other threads can have a chance to grab the connect_lock */
903 CAMEL_SERVICE_REC_UNLOCK (store, connect_lock);
905 /* check for an exception */
906 if (camel_exception_is_set (&local_ex)) {
907 camel_exception_xfer (ex, &local_ex);
911 /* Re-lock the connect_lock */
912 CAMEL_SERVICE_REC_LOCK (store, connect_lock);
915 /* Save the summary */
916 imap_sync_offline (folder, ex);
918 CAMEL_SERVICE_REC_UNLOCK (store, connect_lock);
922 uid_compar (const void *va, const void *vb)
924 const char **sa = (const char **)va, **sb = (const char **)vb;
927 a = strtoul (*sa, NULL, 10);
928 b = strtoul (*sb, NULL, 10);
938 imap_expunge_uids_offline (CamelFolder *folder, GPtrArray *uids, CamelException *ex)
940 CamelFolderChangeInfo *changes;
943 qsort (uids->pdata, uids->len, sizeof (void *), uid_compar);
945 changes = camel_folder_change_info_new ();
947 for (i = 0; i < uids->len; i++) {
948 camel_folder_summary_remove_uid (folder->summary, uids->pdata[i]);
949 camel_folder_change_info_remove_uid (changes, uids->pdata[i]);
950 /* We intentionally don't remove it from the cache because
951 * the cached data may be useful in replaying a COPY later.
954 camel_folder_summary_save (folder->summary);
956 camel_disco_diary_log (CAMEL_DISCO_STORE (folder->parent_store)->diary,
957 CAMEL_DISCO_DIARY_FOLDER_EXPUNGE, folder, uids);
959 camel_object_trigger_event (CAMEL_OBJECT (folder), "folder_changed", changes);
960 camel_folder_change_info_free (changes);
964 imap_expunge_uids_online (CamelFolder *folder, GPtrArray *uids, CamelException *ex)
966 CamelImapStore *store = CAMEL_IMAP_STORE (folder->parent_store);
967 CamelImapResponse *response;
971 CAMEL_SERVICE_REC_LOCK (store, connect_lock);
973 if ((store->capabilities & IMAP_CAPABILITY_UIDPLUS) == 0) {
974 ((CamelFolderClass *)CAMEL_OBJECT_GET_CLASS(folder))->sync(folder, 0, ex);
975 if (camel_exception_is_set(ex)) {
976 CAMEL_SERVICE_REC_UNLOCK (store, connect_lock);
981 qsort (uids->pdata, uids->len, sizeof (void *), uid_compar);
983 while (uid < uids->len) {
984 set = imap_uid_array_to_set (folder->summary, uids, uid, UID_SET_LIMIT, &uid);
985 response = camel_imap_command (store, folder, ex,
986 "UID STORE %s +FLAGS.SILENT (\\Deleted)",
989 camel_imap_response_free (store, response);
990 if (camel_exception_is_set (ex)) {
991 CAMEL_SERVICE_REC_UNLOCK (store, connect_lock);
996 if (store->capabilities & IMAP_CAPABILITY_UIDPLUS) {
997 response = camel_imap_command (store, folder, ex,
998 "UID EXPUNGE %s", set);
1000 response = camel_imap_command (store, folder, ex, "EXPUNGE");
1003 camel_imap_response_free (store, response);
1006 CAMEL_SERVICE_REC_UNLOCK (store, connect_lock);
1010 imap_expunge_uids_resyncing (CamelFolder *folder, GPtrArray *uids, CamelException *ex)
1012 CamelImapFolder *imap_folder = CAMEL_IMAP_FOLDER (folder);
1013 CamelImapStore *store = CAMEL_IMAP_STORE (folder->parent_store);
1014 GPtrArray *keep_uids, *mark_uids;
1015 CamelImapResponse *response;
1018 if (imap_folder->read_only)
1021 if (store->capabilities & IMAP_CAPABILITY_UIDPLUS) {
1022 imap_expunge_uids_online (folder, uids, ex);
1026 /* If we don't have UID EXPUNGE we need to avoid expunging any
1027 * of the wrong messages. So we search for deleted messages,
1028 * and any that aren't in our to-expunge list get temporarily
1029 * marked un-deleted.
1032 CAMEL_SERVICE_REC_LOCK (store, connect_lock);
1034 ((CamelFolderClass *)CAMEL_OBJECT_GET_CLASS(folder))->sync(folder, 0, ex);
1035 if (camel_exception_is_set(ex)) {
1036 CAMEL_SERVICE_REC_UNLOCK (store, connect_lock);
1040 response = camel_imap_command (store, folder, ex, "UID SEARCH DELETED");
1042 CAMEL_SERVICE_REC_UNLOCK (store, connect_lock);
1045 result = camel_imap_response_extract (store, response, "SEARCH", ex);
1047 CAMEL_SERVICE_REC_UNLOCK (store, connect_lock);
1051 if (result[8] == ' ') {
1052 char *uid, *lasts = NULL;
1053 unsigned long euid, kuid;
1056 keep_uids = g_ptr_array_new ();
1057 mark_uids = g_ptr_array_new ();
1059 /* Parse SEARCH response */
1060 for (uid = strtok_r (result + 9, " ", &lasts); uid; uid = strtok_r (NULL, " ", &lasts))
1061 g_ptr_array_add (keep_uids, uid);
1062 qsort (keep_uids->pdata, keep_uids->len,
1063 sizeof (void *), uid_compar);
1065 /* Fill in "mark_uids", empty out "keep_uids" as needed */
1066 for (ei = ki = 0; ei < uids->len; ei++) {
1067 euid = strtoul (uids->pdata[ei], NULL, 10);
1069 for (kuid = 0; ki < keep_uids->len; ki++) {
1070 kuid = strtoul (keep_uids->pdata[ki], NULL, 10);
1077 g_ptr_array_remove_index (keep_uids, ki);
1079 g_ptr_array_add (mark_uids, uids->pdata[ei]);
1082 /* Empty SEARCH result, meaning nothing is marked deleted
1090 /* Unmark messages to be kept */
1096 while (uid < keep_uids->len) {
1097 uidset = imap_uid_array_to_set (folder->summary, keep_uids, uid, UID_SET_LIMIT, &uid);
1099 response = camel_imap_command (store, folder, ex,
1100 "UID STORE %s -FLAGS.SILENT (\\Deleted)",
1106 g_ptr_array_free (keep_uids, TRUE);
1107 g_ptr_array_free (mark_uids, TRUE);
1108 CAMEL_SERVICE_REC_UNLOCK (store, connect_lock);
1111 camel_imap_response_free (store, response);
1115 /* Mark any messages that still need to be marked */
1120 while (uid < mark_uids->len) {
1121 uidset = imap_uid_array_to_set (folder->summary, mark_uids, uid, UID_SET_LIMIT, &uid);
1123 response = camel_imap_command (store, folder, ex,
1124 "UID STORE %s +FLAGS.SILENT (\\Deleted)",
1130 g_ptr_array_free (keep_uids, TRUE);
1131 g_ptr_array_free (mark_uids, TRUE);
1132 CAMEL_SERVICE_REC_UNLOCK (store, connect_lock);
1135 camel_imap_response_free (store, response);
1138 if (mark_uids != uids)
1139 g_ptr_array_free (mark_uids, TRUE);
1142 /* Do the actual expunging */
1143 response = camel_imap_command (store, folder, ex, "EXPUNGE");
1145 camel_imap_response_free (store, response);
1147 /* And fix the remaining messages if we mangled them */
1152 while (uid < keep_uids->len) {
1153 uidset = imap_uid_array_to_set (folder->summary, keep_uids, uid, UID_SET_LIMIT, &uid);
1155 /* Don't pass ex if it's already been set */
1156 response = camel_imap_command (store, folder,
1157 camel_exception_is_set (ex) ? NULL : ex,
1158 "UID STORE %s +FLAGS.SILENT (\\Deleted)",
1163 camel_imap_response_free (store, response);
1166 g_ptr_array_free (keep_uids, TRUE);
1169 /* now we can free this, now that we're done with keep_uids */
1172 CAMEL_SERVICE_REC_UNLOCK (store, connect_lock);
1180 static int counter = 0;
1181 G_LOCK_DEFINE_STATIC (lock);
1184 res = g_strdup_printf ("tempuid-%lx-%d",
1185 (unsigned long) time (NULL),
1193 imap_append_offline (CamelFolder *folder, CamelMimeMessage *message,
1194 const CamelMessageInfo *info, char **appended_uid,
1197 CamelImapStore *imap_store = CAMEL_IMAP_STORE (folder->parent_store);
1198 CamelImapMessageCache *cache = CAMEL_IMAP_FOLDER (folder)->cache;
1199 CamelFolderChangeInfo *changes;
1202 uid = get_temp_uid ();
1204 camel_imap_summary_add_offline (folder->summary, uid, message, info);
1205 CAMEL_IMAP_FOLDER_REC_LOCK (folder, cache_lock);
1206 camel_imap_message_cache_insert_wrapper (cache, uid, "",
1207 CAMEL_DATA_WRAPPER (message), ex);
1208 CAMEL_IMAP_FOLDER_REC_UNLOCK (folder, cache_lock);
1210 changes = camel_folder_change_info_new ();
1211 camel_folder_change_info_add_uid (changes, uid);
1212 camel_object_trigger_event (CAMEL_OBJECT (folder), "folder_changed",
1214 camel_folder_change_info_free (changes);
1216 camel_disco_diary_log (CAMEL_DISCO_STORE (imap_store)->diary,
1217 CAMEL_DISCO_DIARY_FOLDER_APPEND, folder, uid);
1219 *appended_uid = uid;
1224 static CamelImapResponse *
1225 do_append (CamelFolder *folder, CamelMimeMessage *message,
1226 const CamelMessageInfo *info, char **uid,
1229 CamelImapStore *store = CAMEL_IMAP_STORE (folder->parent_store);
1230 CamelImapResponse *response, *response2;
1231 CamelStream *memstream;
1232 CamelMimeFilter *crlf_filter;
1233 CamelStreamFilter *streamfilter;
1235 char *flagstr, *end;
1238 /* encode any 8bit parts so we avoid sending embedded nul-chars and such */
1239 camel_mime_message_encode_8bit_parts (message);
1241 /* FIXME: We could avoid this if we knew how big the message was. */
1242 memstream = camel_stream_mem_new ();
1243 ba = g_byte_array_new ();
1244 camel_stream_mem_set_byte_array (CAMEL_STREAM_MEM (memstream), ba);
1246 streamfilter = camel_stream_filter_new_with_stream (memstream);
1247 crlf_filter = camel_mime_filter_crlf_new (CAMEL_MIME_FILTER_CRLF_ENCODE,
1248 CAMEL_MIME_FILTER_CRLF_MODE_CRLF_ONLY);
1249 camel_stream_filter_add (streamfilter, crlf_filter);
1250 camel_data_wrapper_write_to_stream (CAMEL_DATA_WRAPPER (message),
1251 CAMEL_STREAM (streamfilter));
1252 camel_object_unref (CAMEL_OBJECT (streamfilter));
1253 camel_object_unref (CAMEL_OBJECT (crlf_filter));
1254 camel_object_unref (CAMEL_OBJECT (memstream));
1256 /* Some servers dont let us append with custom flags. If the command fails for
1257 whatever reason, assume this is the case and save the state and try again */
1260 flags = camel_message_info_flags(info);
1261 if (!store->nocustomappend)
1262 flags |= imap_label_to_flags((CamelMessageInfo *)info);
1265 flags &= folder->permanent_flags;
1267 flagstr = imap_create_flag_list (flags);
1271 response = camel_imap_command (store, NULL, ex, "APPEND %F%s%s {%d}",
1272 folder->full_name, flagstr ? " " : "",
1273 flagstr ? flagstr : "", ba->len);
1277 if (camel_exception_get_id(ex) == CAMEL_EXCEPTION_SERVICE_INVALID && !store->nocustomappend) {
1278 camel_exception_clear(ex);
1279 store->nocustomappend = 1;
1282 g_byte_array_free (ba, TRUE);
1286 if (*response->status != '+') {
1287 camel_imap_response_free (store, response);
1288 g_byte_array_free (ba, TRUE);
1292 /* send the rest of our data - the mime message */
1293 response2 = camel_imap_command_continuation (store, (const char *) ba->data, ba->len, ex);
1294 g_byte_array_free (ba, TRUE);
1296 /* free it only after message is sent. This may cause more FETCHes. */
1297 camel_imap_response_free (store, response);
1301 if (store->capabilities & IMAP_CAPABILITY_UIDPLUS) {
1302 *uid = camel_strstrcase (response2->status, "[APPENDUID ");
1304 *uid = strchr (*uid + 11, ' ');
1306 *uid = g_strndup (*uid + 1, strcspn (*uid + 1, "]"));
1307 /* Make sure it's a number */
1308 if (strtoul (*uid, &end, 10) == 0 || *end) {
1320 imap_append_online (CamelFolder *folder, CamelMimeMessage *message,
1321 const CamelMessageInfo *info, char **appended_uid,
1324 CamelImapStore *store = CAMEL_IMAP_STORE (folder->parent_store);
1325 CamelImapResponse *response;
1329 count = camel_folder_summary_count (folder->summary);
1330 response = do_append (folder, message, info, &uid, ex);
1335 /* Cache first, since freeing response may trigger a
1336 * summary update that will want this information.
1338 CAMEL_IMAP_FOLDER_REC_LOCK (folder, cache_lock);
1339 camel_imap_message_cache_insert_wrapper (
1340 CAMEL_IMAP_FOLDER (folder)->cache, uid,
1341 "", CAMEL_DATA_WRAPPER (message), ex);
1342 CAMEL_IMAP_FOLDER_REC_UNLOCK (folder, cache_lock);
1344 *appended_uid = uid;
1347 } else if (appended_uid)
1348 *appended_uid = NULL;
1350 camel_imap_response_free (store, response);
1352 /* Make sure a "folder_changed" is emitted. */
1353 CAMEL_SERVICE_REC_LOCK (store, connect_lock);
1354 if (store->current_folder != folder ||
1355 camel_folder_summary_count (folder->summary) == count)
1356 imap_refresh_info (folder, ex);
1357 CAMEL_SERVICE_REC_UNLOCK (store, connect_lock);
1361 imap_append_resyncing (CamelFolder *folder, CamelMimeMessage *message,
1362 const CamelMessageInfo *info, char **appended_uid,
1365 CamelImapStore *store = CAMEL_IMAP_STORE (folder->parent_store);
1366 CamelImapResponse *response;
1369 response = do_append (folder, message, info, &uid, ex);
1374 CamelImapFolder *imap_folder = CAMEL_IMAP_FOLDER (folder);
1375 const char *olduid = camel_message_info_uid (info);
1377 CAMEL_IMAP_FOLDER_REC_LOCK (imap_folder, cache_lock);
1378 camel_imap_message_cache_copy (imap_folder->cache, olduid,
1379 imap_folder->cache, uid, ex);
1380 CAMEL_IMAP_FOLDER_REC_UNLOCK (imap_folder, cache_lock);
1383 *appended_uid = uid;
1386 } else if (appended_uid)
1387 *appended_uid = NULL;
1389 camel_imap_response_free (store, response);
1394 imap_transfer_offline (CamelFolder *source, GPtrArray *uids,
1395 CamelFolder *dest, GPtrArray **transferred_uids,
1396 gboolean delete_originals, CamelException *ex)
1398 CamelImapStore *store = CAMEL_IMAP_STORE (source->parent_store);
1399 CamelImapMessageCache *sc = CAMEL_IMAP_FOLDER (source)->cache;
1400 CamelImapMessageCache *dc = CAMEL_IMAP_FOLDER (dest)->cache;
1401 CamelFolderChangeInfo *changes;
1402 CamelMimeMessage *message;
1403 CamelMessageInfo *mi;
1404 char *uid, *destuid;
1407 /* We grab the store's command lock first, and then grab the
1408 * source and destination cache_locks. This way we can't
1409 * deadlock in the case where we're simultaneously also trying
1410 * to copy messages in the other direction from another thread.
1412 CAMEL_SERVICE_REC_LOCK (store, connect_lock);
1413 CAMEL_IMAP_FOLDER_REC_LOCK (source, cache_lock);
1414 CAMEL_IMAP_FOLDER_REC_LOCK (dest, cache_lock);
1415 CAMEL_SERVICE_REC_UNLOCK (store, connect_lock);
1417 if (transferred_uids) {
1418 *transferred_uids = g_ptr_array_new ();
1419 g_ptr_array_set_size (*transferred_uids, uids->len);
1422 changes = camel_folder_change_info_new ();
1424 for (i = 0; i < uids->len; i++) {
1425 uid = uids->pdata[i];
1427 destuid = get_temp_uid ();
1429 mi = camel_folder_summary_uid (source->summary, uid);
1430 g_return_if_fail (mi != NULL);
1432 message = camel_folder_get_message (source, uid, NULL);
1435 camel_imap_summary_add_offline (dest->summary, destuid, message, mi);
1436 camel_object_unref (CAMEL_OBJECT (message));
1438 camel_imap_summary_add_offline_uncached (dest->summary, destuid, mi);
1440 camel_imap_message_cache_copy (sc, uid, dc, destuid, ex);
1441 camel_message_info_free(mi);
1443 camel_folder_change_info_add_uid (changes, destuid);
1444 if (transferred_uids)
1445 (*transferred_uids)->pdata[i] = destuid;
1449 if (delete_originals)
1450 camel_folder_delete_message (source, uid);
1453 CAMEL_IMAP_FOLDER_REC_UNLOCK (dest, cache_lock);
1454 CAMEL_IMAP_FOLDER_REC_UNLOCK (source, cache_lock);
1456 camel_object_trigger_event (CAMEL_OBJECT (dest), "folder_changed", changes);
1457 camel_folder_change_info_free (changes);
1459 camel_disco_diary_log (CAMEL_DISCO_STORE (store)->diary,
1460 CAMEL_DISCO_DIARY_FOLDER_TRANSFER,
1461 source, dest, uids, delete_originals);
1465 handle_copyuid (CamelImapResponse *response, CamelFolder *source,
1466 CamelFolder *destination)
1468 CamelImapMessageCache *scache = CAMEL_IMAP_FOLDER (source)->cache;
1469 CamelImapMessageCache *dcache = CAMEL_IMAP_FOLDER (destination)->cache;
1470 char *validity, *srcset, *destset;
1471 GPtrArray *src, *dest;
1474 validity = camel_strstrcase (response->status, "[COPYUID ");
1478 if (strtoul (validity, NULL, 10) !=
1479 CAMEL_IMAP_SUMMARY (destination->summary)->validity)
1482 srcset = strchr (validity, ' ');
1485 destset = strchr (srcset, ' ');
1489 src = imap_uid_set_to_array (source->summary, srcset);
1490 dest = imap_uid_set_to_array (destination->summary, destset);
1492 if (src && dest && src->len == dest->len) {
1493 /* We don't have to worry about deadlocking on the
1494 * cache locks here, because we've got the store's
1495 * command lock too, so no one else could be here.
1497 CAMEL_IMAP_FOLDER_REC_LOCK (source, cache_lock);
1498 CAMEL_IMAP_FOLDER_REC_LOCK (destination, cache_lock);
1499 for (i = 0; i < src->len; i++) {
1500 camel_imap_message_cache_copy (scache, src->pdata[i],
1501 dcache, dest->pdata[i],
1504 CAMEL_IMAP_FOLDER_REC_UNLOCK (source, cache_lock);
1505 CAMEL_IMAP_FOLDER_REC_UNLOCK (destination, cache_lock);
1507 imap_uid_array_free (src);
1508 imap_uid_array_free (dest);
1512 imap_uid_array_free (src);
1513 imap_uid_array_free (dest);
1515 g_warning ("Bad COPYUID response from server");
1519 do_copy (CamelFolder *source, GPtrArray *uids,
1520 CamelFolder *destination, int delete_originals, CamelException *ex)
1522 CamelImapStore *store = CAMEL_IMAP_STORE (source->parent_store);
1523 CamelImapResponse *response;
1525 int uid = 0, last=0, i;
1527 while (uid < uids->len && !camel_exception_is_set (ex)) {
1528 uidset = imap_uid_array_to_set (source->summary, uids, uid, UID_SET_LIMIT, &uid);
1530 if ((store->capabilities & IMAP_CAPABILITY_XGWMOVE) && delete_originals) {
1531 response = camel_imap_command (store, source, ex, "UID XGWMOVE %s %F", uidset, destination->full_name);
1532 /* TODO: EXPUNGE returns??? */
1533 camel_imap_response_free (store, response);
1535 response = camel_imap_command (store, source, ex, "UID COPY %s %F", uidset, destination->full_name);
1536 if (response && (store->capabilities & IMAP_CAPABILITY_UIDPLUS))
1537 handle_copyuid (response, source, destination);
1538 camel_imap_response_free (store, response);
1540 if (!camel_exception_is_set(ex) && delete_originals) {
1541 for (i=last;i<uid;i++)
1542 camel_folder_delete_message(source, uids->pdata[i]);
1551 imap_transfer_online (CamelFolder *source, GPtrArray *uids,
1552 CamelFolder *dest, GPtrArray **transferred_uids,
1553 gboolean delete_originals, CamelException *ex)
1555 CamelImapStore *store = CAMEL_IMAP_STORE (source->parent_store);
1558 /* Sync message flags if needed. */
1559 imap_sync_online (source, ex);
1560 if (camel_exception_is_set (ex))
1563 count = camel_folder_summary_count (dest->summary);
1565 qsort (uids->pdata, uids->len, sizeof (void *), uid_compar);
1567 /* Now copy the messages */
1568 do_copy(source, uids, dest, delete_originals, ex);
1569 if (camel_exception_is_set (ex))
1572 /* Make the destination notice its new messages */
1573 if (store->current_folder != dest ||
1574 camel_folder_summary_count (dest->summary) == count)
1575 camel_folder_refresh_info (dest, ex);
1578 if (transferred_uids)
1579 *transferred_uids = NULL;
1583 imap_transfer_resyncing (CamelFolder *source, GPtrArray *uids,
1584 CamelFolder *dest, GPtrArray **transferred_uids,
1585 gboolean delete_originals, CamelException *ex)
1587 CamelDiscoDiary *diary = CAMEL_DISCO_STORE (source->parent_store)->diary;
1588 GPtrArray *realuids;
1591 CamelMimeMessage *message;
1592 CamelMessageInfo *info;
1594 qsort (uids->pdata, uids->len, sizeof (void *), uid_compar);
1596 /* This is trickier than append_resyncing, because some of
1597 * the messages we are copying may have been copied or
1598 * appended into @source while we were offline, in which case
1599 * if we don't have UIDPLUS, we won't know their real UIDs,
1600 * so we'll have to append them rather than copying.
1603 realuids = g_ptr_array_new ();
1606 while (i < uids->len) {
1607 /* Skip past real UIDs */
1608 for (first = i; i < uids->len; i++) {
1609 uid = uids->pdata[i];
1611 if (!isdigit ((unsigned char)*uid)) {
1612 uid = camel_disco_diary_uidmap_lookup (diary, uid);
1616 g_ptr_array_add (realuids, (char *)uid);
1619 /* If we saw any real UIDs, do a COPY */
1621 do_copy (source, realuids, dest, delete_originals, ex);
1622 g_ptr_array_set_size (realuids, 0);
1623 if (i == uids->len || camel_exception_is_set (ex))
1627 /* Deal with fake UIDs */
1628 while (i < uids->len &&
1629 !isdigit (*(unsigned char *)(uids->pdata[i])) &&
1630 !camel_exception_is_set (ex)) {
1631 uid = uids->pdata[i];
1632 message = camel_folder_get_message (source, uid, NULL);
1634 /* Message must have been expunged */
1637 info = camel_folder_get_message_info (source, uid);
1638 g_return_if_fail (info != NULL);
1640 imap_append_online (dest, message, info, NULL, ex);
1641 camel_folder_free_message_info (source, info);
1642 camel_object_unref (CAMEL_OBJECT (message));
1643 if (delete_originals)
1644 camel_folder_delete_message (source, uid);
1649 g_ptr_array_free (realuids, FALSE);
1652 if (transferred_uids)
1653 *transferred_uids = NULL;
1657 imap_search_by_expression (CamelFolder *folder, const char *expression, CamelException *ex)
1659 CamelImapFolder *imap_folder = CAMEL_IMAP_FOLDER (folder);
1662 /* we could get around this by creating a new search object each time,
1663 but i doubt its worth it since any long operation would lock the
1664 command channel too */
1665 CAMEL_IMAP_FOLDER_LOCK(folder, search_lock);
1667 camel_folder_search_set_folder (imap_folder->search, folder);
1668 matches = camel_folder_search_search(imap_folder->search, expression, NULL, ex);
1670 CAMEL_IMAP_FOLDER_UNLOCK(folder, search_lock);
1676 imap_search_by_uids(CamelFolder *folder, const char *expression, GPtrArray *uids, CamelException *ex)
1678 CamelImapFolder *imap_folder = CAMEL_IMAP_FOLDER(folder);
1682 return g_ptr_array_new();
1684 CAMEL_IMAP_FOLDER_LOCK(folder, search_lock);
1686 camel_folder_search_set_folder(imap_folder->search, folder);
1687 matches = camel_folder_search_search(imap_folder->search, expression, uids, ex);
1689 CAMEL_IMAP_FOLDER_UNLOCK(folder, search_lock);
1695 imap_search_free (CamelFolder *folder, GPtrArray *uids)
1697 CamelImapFolder *imap_folder = CAMEL_IMAP_FOLDER (folder);
1699 g_return_if_fail (imap_folder->search);
1701 CAMEL_IMAP_FOLDER_LOCK(folder, search_lock);
1703 camel_folder_search_free_result (imap_folder->search, uids);
1705 CAMEL_IMAP_FOLDER_UNLOCK(folder, search_lock);
1708 static CamelMimeMessage *get_message (CamelImapFolder *imap_folder,
1710 CamelMessageContentInfo *ci,
1711 CamelException *ex);
1713 struct _part_spec_stack {
1714 struct _part_spec_stack *parent;
1719 part_spec_push (struct _part_spec_stack **stack, int part)
1721 struct _part_spec_stack *node;
1723 node = g_new (struct _part_spec_stack, 1);
1724 node->parent = *stack;
1731 part_spec_pop (struct _part_spec_stack **stack)
1733 struct _part_spec_stack *node;
1736 g_return_val_if_fail (*stack != NULL, 0);
1739 *stack = node->parent;
1748 content_info_get_part_spec (CamelMessageContentInfo *ci)
1750 struct _part_spec_stack *stack = NULL;
1751 CamelMessageContentInfo *node;
1752 char *part_spec, *buf;
1757 while (node->parent) {
1758 CamelMessageContentInfo *child;
1760 /* FIXME: is this only supposed to apply if 'node' is a multipart? */
1761 if (node->parent->parent &&
1762 camel_content_type_is (node->parent->type, "message", "*") &&
1763 !camel_content_type_is (node->parent->parent->type, "message", "*")) {
1764 node = node->parent;
1768 child = node->parent->childs;
1769 for (part = 1; child; part++) {
1773 child = child->next;
1776 part_spec_push (&stack, part);
1779 while ((part = part / 10))
1782 node = node->parent;
1785 buf = part_spec = g_malloc (len);
1786 part_spec[0] = '\0';
1789 part = part_spec_pop (&stack);
1790 buf += sprintf (buf, "%d%s", part, stack ? "." : "");
1796 /* Fetch the contents of the MIME part indicated by @ci, which is part
1797 * of message @uid in @folder.
1799 static CamelDataWrapper *
1800 get_content (CamelImapFolder *imap_folder, const char *uid,
1801 CamelMimePart *part, CamelMessageContentInfo *ci,
1805 CamelDataWrapper *content = NULL;
1806 CamelStream *stream;
1809 part_spec = content_info_get_part_spec (ci);
1811 d(printf("get content '%s' '%s' (frommsg = %d)\n", part_spec, camel_content_type_format(ci->type), frommsg));
1813 /* There are three cases: multipart/signed, multipart, message/rfc822, and "other" */
1814 if (camel_content_type_is (ci->type, "multipart", "signed")) {
1815 CamelMultipartSigned *body_mp;
1819 /* Note: because we get the content parts uninterpreted anyway, we could potentially
1820 just use the normalmultipart code, except that multipart/signed wont let you yet! */
1822 body_mp = camel_multipart_signed_new ();
1823 /* need to set this so it grabs the boundary and other info about the signed type */
1824 /* we assume that part->content_type is more accurate/full than ci->type */
1825 camel_data_wrapper_set_mime_type_field (CAMEL_DATA_WRAPPER (body_mp), CAMEL_DATA_WRAPPER (part)->mime_type);
1827 spec = g_alloca(strlen(part_spec) + 6);
1829 sprintf(spec, part_spec[0] ? "%s.TEXT" : "TEXT", part_spec);
1831 strcpy(spec, part_spec);
1834 stream = camel_imap_folder_fetch_data (imap_folder, uid, spec, FALSE, ex);
1836 ret = camel_data_wrapper_construct_from_stream (CAMEL_DATA_WRAPPER (body_mp), stream);
1837 camel_object_unref (CAMEL_OBJECT (stream));
1839 camel_object_unref ((CamelObject *) body_mp);
1844 return (CamelDataWrapper *) body_mp;
1845 } else if (camel_content_type_is (ci->type, "multipart", "*")) {
1846 CamelMultipart *body_mp;
1848 int speclen, num, isdigest;
1850 if (camel_content_type_is (ci->type, "multipart", "encrypted"))
1851 body_mp = (CamelMultipart *) camel_multipart_encrypted_new ();
1853 body_mp = camel_multipart_new ();
1855 /* need to set this so it grabs the boundary and other info about the multipart */
1856 /* we assume that part->content_type is more accurate/full than ci->type */
1857 camel_data_wrapper_set_mime_type_field (CAMEL_DATA_WRAPPER (body_mp), CAMEL_DATA_WRAPPER (part)->mime_type);
1858 isdigest = camel_content_type_is(((CamelDataWrapper *)part)->mime_type, "multipart", "digest");
1860 speclen = strlen (part_spec);
1861 child_spec = g_malloc (speclen + 17); /* dot + 10 + dot + MIME + nul */
1862 memcpy (child_spec, part_spec, speclen);
1864 child_spec[speclen++] = '.';
1870 sprintf (child_spec + speclen, "%d.MIME", num++);
1871 stream = camel_imap_folder_fetch_data (imap_folder, uid, child_spec, FALSE, ex);
1875 part = camel_mime_part_new ();
1876 ret = camel_data_wrapper_construct_from_stream (CAMEL_DATA_WRAPPER (part), stream);
1877 camel_object_unref (CAMEL_OBJECT (stream));
1879 camel_object_unref (CAMEL_OBJECT (part));
1880 camel_object_unref (CAMEL_OBJECT (body_mp));
1881 g_free (child_spec);
1885 content = get_content (imap_folder, uid, part, ci, FALSE, ex);
1888 if (!stream || !content) {
1889 camel_object_unref (CAMEL_OBJECT (body_mp));
1890 g_free (child_spec);
1894 if (camel_debug("imap:folder")) {
1895 char *ct = camel_content_type_format(camel_mime_part_get_content_type((CamelMimePart *)part));
1896 char *ct2 = camel_content_type_format(ci->type);
1898 printf("Setting part content type to '%s' contentinfo type is '%s'\n", ct, ct2);
1903 /* if we had no content-type header on a multipart/digest sub-part, then we need to
1904 treat it as message/rfc822 instead */
1905 if (isdigest && camel_medium_get_header((CamelMedium *)part, "content-type") == NULL) {
1906 CamelContentType *ct = camel_content_type_new("message", "rfc822");
1908 camel_data_wrapper_set_mime_type_field(content, ct);
1909 camel_content_type_unref(ct);
1911 camel_data_wrapper_set_mime_type_field(content, camel_mime_part_get_content_type(part));
1914 camel_medium_set_content_object (CAMEL_MEDIUM (part), content);
1915 camel_object_unref(content);
1917 camel_multipart_add_part (body_mp, part);
1918 camel_object_unref(part);
1923 g_free (child_spec);
1925 return (CamelDataWrapper *) body_mp;
1926 } else if (camel_content_type_is (ci->type, "message", "rfc822")) {
1927 content = (CamelDataWrapper *) get_message (imap_folder, uid, ci->childs, ex);
1931 CamelTransferEncoding enc;
1934 /* NB: we need this differently to multipart/signed case above on purpose */
1935 spec = g_alloca(strlen(part_spec) + 6);
1937 sprintf(spec, part_spec[0] ? "%s.1" : "1", part_spec);
1939 strcpy(spec, part_spec[0]?part_spec:"1");
1941 enc = ci->encoding?camel_transfer_encoding_from_string(ci->encoding):CAMEL_TRANSFER_ENCODING_DEFAULT;
1942 content = camel_imap_wrapper_new (imap_folder, ci->type, enc, uid, spec, part);
1948 static CamelMimeMessage *
1949 get_message (CamelImapFolder *imap_folder, const char *uid,
1950 CamelMessageContentInfo *ci,
1953 CamelImapStore *store = CAMEL_IMAP_STORE (CAMEL_FOLDER (imap_folder)->parent_store);
1954 CamelDataWrapper *content;
1955 CamelMimeMessage *msg;
1956 CamelStream *stream;
1957 char *section_text, *part_spec;
1960 part_spec = content_info_get_part_spec(ci);
1961 d(printf("get message '%s'\n", part_spec));
1962 section_text = g_strdup_printf ("%s%s%s", part_spec, *part_spec ? "." : "",
1963 store->server_level >= IMAP_LEVEL_IMAP4REV1 ? "HEADER" : "0");
1965 stream = camel_imap_folder_fetch_data (imap_folder, uid, section_text, FALSE, ex);
1966 g_free (section_text);
1971 msg = camel_mime_message_new ();
1972 ret = camel_data_wrapper_construct_from_stream (CAMEL_DATA_WRAPPER (msg), stream);
1973 camel_object_unref (CAMEL_OBJECT (stream));
1975 camel_object_unref (CAMEL_OBJECT (msg));
1979 content = get_content (imap_folder, uid, CAMEL_MIME_PART (msg), ci, TRUE, ex);
1981 camel_object_unref (CAMEL_OBJECT (msg));
1985 if (camel_debug("imap:folder")) {
1986 char *ct = camel_content_type_format(camel_mime_part_get_content_type((CamelMimePart *)msg));
1987 char *ct2 = camel_content_type_format(ci->type);
1989 printf("Setting message content type to '%s' contentinfo type is '%s'\n", ct, ct2);
1994 camel_data_wrapper_set_mime_type_field(content, camel_mime_part_get_content_type((CamelMimePart *)msg));
1995 camel_medium_set_content_object (CAMEL_MEDIUM (msg), content);
1996 camel_object_unref (CAMEL_OBJECT (content));
2001 #define IMAP_SMALL_BODY_SIZE 5120
2003 static CamelMimeMessage *
2004 get_message_simple (CamelImapFolder *imap_folder, const char *uid,
2005 CamelStream *stream, CamelException *ex)
2007 CamelMimeMessage *msg;
2011 stream = camel_imap_folder_fetch_data (imap_folder, uid, "",
2017 msg = camel_mime_message_new ();
2018 ret = camel_data_wrapper_construct_from_stream (CAMEL_DATA_WRAPPER (msg),
2020 camel_object_unref (CAMEL_OBJECT (stream));
2022 camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
2023 _("Unable to retrieve message: %s"),
2024 g_strerror (errno));
2025 camel_object_unref (CAMEL_OBJECT (msg));
2033 content_info_incomplete (CamelMessageContentInfo *ci)
2038 if (camel_content_type_is (ci->type, "multipart", "*")
2039 || camel_content_type_is (ci->type, "message", "rfc822")) {
2042 for (ci = ci->childs;ci;ci=ci->next)
2043 if (content_info_incomplete(ci))
2050 static CamelMimeMessage *
2051 imap_get_message (CamelFolder *folder, const char *uid, CamelException *ex)
2053 CamelImapFolder *imap_folder = CAMEL_IMAP_FOLDER (folder);
2054 CamelImapStore *store = CAMEL_IMAP_STORE (folder->parent_store);
2055 CamelImapMessageInfo *mi;
2056 CamelMimeMessage *msg = NULL;
2057 CamelStream *stream = NULL;
2060 mi = (CamelImapMessageInfo *)camel_folder_summary_uid (folder->summary, uid);
2062 camel_exception_setv(ex, CAMEL_EXCEPTION_FOLDER_INVALID_UID,
2063 _("Cannot get message: %s\n %s"), uid, _("No such message"));
2067 /* If its cached in full, just get it as is, this is only a shortcut,
2068 since we get stuff from the cache anyway. It affects a busted connection though. */
2069 if ( (stream = camel_imap_folder_fetch_data(imap_folder, uid, "", TRUE, NULL))
2070 && (msg = get_message_simple(imap_folder, uid, stream, ex)))
2073 /* All this mess is so we silently retry a fetch if we fail with
2074 service_unavailable, without an (equivalent) mess of gotos */
2078 camel_exception_clear(ex);
2080 /* If the message is small or only 1 part, or server doesn't do 4v1 (properly) fetch it in one piece. */
2081 if (store->server_level < IMAP_LEVEL_IMAP4REV1
2082 || store->braindamaged
2083 || mi->info.size < IMAP_SMALL_BODY_SIZE
2084 || (!content_info_incomplete(mi->info.content) && !mi->info.content->childs)) {
2085 msg = get_message_simple (imap_folder, uid, NULL, ex);
2087 if (content_info_incomplete (mi->info.content)) {
2088 /* For larger messages, fetch the structure and build a message
2089 * with offline parts. (We check mi->content->type rather than
2090 * mi->content because camel_folder_summary_info_new always creates
2091 * an empty content struct.)
2093 CamelImapResponse *response;
2094 GData *fetch_data = NULL;
2095 char *body, *found_uid;
2098 CAMEL_SERVICE_REC_LOCK(store, connect_lock);
2099 if (!camel_imap_store_connected(store, ex)) {
2100 CAMEL_SERVICE_REC_UNLOCK(store, connect_lock);
2101 camel_exception_set (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
2102 _("This message is not currently available"));
2106 response = camel_imap_command (store, folder, ex, "UID FETCH %s BODY", uid);
2107 CAMEL_SERVICE_REC_UNLOCK(store, connect_lock);
2110 for (i = 0, body = NULL; i < response->untagged->len; i++) {
2111 fetch_data = parse_fetch_response (imap_folder, response->untagged->pdata[i]);
2113 found_uid = g_datalist_get_data (&fetch_data, "UID");
2114 body = g_datalist_get_data (&fetch_data, "BODY");
2115 if (found_uid && body && !strcmp (found_uid, uid))
2117 g_datalist_clear (&fetch_data);
2124 /* NB: small race here, setting the info.content */
2125 imap_parse_body ((const char **) &body, folder, mi->info.content);
2126 camel_folder_summary_touch (folder->summary);
2130 g_datalist_clear (&fetch_data);
2132 camel_imap_response_free (store, response);
2134 camel_exception_clear(ex);
2138 if (camel_debug_start("imap:folder")) {
2139 printf("Folder get message '%s' folder info ->\n", uid);
2140 camel_message_info_dump((CamelMessageInfo *)mi);
2144 /* FETCH returned OK, but we didn't parse a BODY
2145 * response. Courier will return invalid BODY
2146 * responses for invalidly MIMEd messages, so
2147 * fall back to fetching the entire thing and
2148 * let the mailer's "bad MIME" code handle it.
2150 if (content_info_incomplete (mi->info.content))
2151 msg = get_message_simple (imap_folder, uid, NULL, ex);
2153 msg = get_message (imap_folder, uid, mi->info.content, ex);
2155 } while (msg == NULL
2157 && camel_exception_get_id(ex) == CAMEL_EXCEPTION_SERVICE_UNAVAILABLE);
2159 done: /* FIXME, this shouldn't be done this way. */
2161 camel_medium_set_header (CAMEL_MEDIUM (msg), "X-Evolution-Source", store->base_url);
2163 camel_message_info_free(&mi->info);
2169 imap_cache_message (CamelDiscoFolder *disco_folder, const char *uid,
2172 CamelImapFolder *imap_folder = CAMEL_IMAP_FOLDER (disco_folder);
2173 CamelStream *stream;
2175 stream = camel_imap_folder_fetch_data (imap_folder, uid, "", FALSE, ex);
2177 camel_object_unref (CAMEL_OBJECT (stream));
2180 /* We pretend that a FLAGS or RFC822.SIZE response is always exactly
2181 * 20 bytes long, and a BODY[HEADERS] response is always 2000 bytes
2182 * long. Since we know how many of each kind of response we're
2183 * expecting, we can find the total (pretend) amount of server traffic
2184 * to expect and then count off the responses as we read them to update
2187 #define IMAP_PRETEND_SIZEOF_FLAGS 20
2188 #define IMAP_PRETEND_SIZEOF_SIZE 20
2189 #define IMAP_PRETEND_SIZEOF_HEADERS 2000
2191 static char *tm_months[] = {
2192 "Jan", "Feb", "Mar", "Apr", "May", "Jun",
2193 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
2197 decode_time (const unsigned char **in, int *hour, int *min, int *sec)
2199 register const unsigned char *inptr;
2200 int *val, colons = 0;
2202 *hour = *min = *sec = 0;
2205 for (inptr = *in; *inptr && !isspace ((int) *inptr); inptr++) {
2206 if (*inptr == ':') {
2218 } else if (!isdigit ((int) *inptr))
2221 *val = (*val * 10) + (*inptr - '0');
2230 decode_internaldate (const unsigned char *in)
2232 const unsigned char *inptr = in;
2233 int hour, min, sec, n;
2238 memset ((void *) &tm, 0, sizeof (struct tm));
2240 tm.tm_mday = strtoul ((char *) inptr, (char **) &buf, 10);
2241 if (buf == inptr || *buf != '-')
2245 if (inptr[3] != '-')
2248 for (n = 0; n < 12; n++) {
2249 if (!g_ascii_strncasecmp ((gchar *) inptr, tm_months[n], 3))
2260 n = strtoul ((char *) inptr, (char **) &buf, 10);
2261 if (buf == inptr || *buf != ' ')
2264 tm.tm_year = n - 1900;
2267 if (!decode_time (&inptr, &hour, &min, &sec))
2274 n = strtol ((char *) inptr, NULL, 10);
2276 date = e_mktime_utc (&tm);
2278 /* date is now GMT of the time we want, but not offset by the timezone ... */
2280 /* this should convert the time to the GMT equiv time */
2281 date -= ((n / 100) * 60 * 60) + (n % 100) * 60;
2287 add_message_from_data (CamelFolder *folder, GPtrArray *messages,
2288 int first, GData *data)
2290 CamelMimeMessage *msg;
2291 CamelStream *stream;
2292 CamelImapMessageInfo *mi;
2296 seq = GPOINTER_TO_INT (g_datalist_get_data (&data, "SEQUENCE"));
2299 stream = g_datalist_get_data (&data, "BODY_PART_STREAM");
2303 if (seq - first >= messages->len)
2304 g_ptr_array_set_size (messages, seq - first + 1);
2306 msg = camel_mime_message_new ();
2307 if (camel_data_wrapper_construct_from_stream (CAMEL_DATA_WRAPPER (msg), stream) == -1) {
2308 camel_object_unref (CAMEL_OBJECT (msg));
2312 mi = (CamelImapMessageInfo *)camel_folder_summary_info_new_from_message (folder->summary, msg);
2313 camel_object_unref (CAMEL_OBJECT (msg));
2315 if ((idate = g_datalist_get_data (&data, "INTERNALDATE")))
2316 mi->info.date_received = decode_internaldate ((const unsigned char *) idate);
2318 if (mi->info.date_received == -1)
2319 mi->info.date_received = mi->info.date_sent;
2321 messages->pdata[seq - first] = mi;
2325 #define CAMEL_MESSAGE_INFO_HEADERS "DATE FROM TO CC SUBJECT REFERENCES IN-REPLY-TO MESSAGE-ID MIME-VERSION CONTENT-TYPE "
2327 /* FIXME: this needs to be kept in sync with camel-mime-utils.c's list
2328 of mailing-list headers and so might be best if this were
2330 #define MAILING_LIST_HEADERS "X-MAILING-LIST X-LOOP LIST-ID LIST-POST MAILING-LIST ORIGINATOR X-LIST SENDER RETURN-PATH X-BEENTHERE "
2333 imap_update_summary (CamelFolder *folder, int exists,
2334 CamelFolderChangeInfo *changes,
2337 CamelImapStore *store = CAMEL_IMAP_STORE (folder->parent_store);
2338 CamelImapFolder *imap_folder = CAMEL_IMAP_FOLDER (folder);
2339 GPtrArray *fetch_data = NULL, *messages = NULL, *needheaders;
2340 guint32 flags, uidval;
2341 int i, seq, first, size, got;
2342 CamelImapResponseType type;
2343 GString *header_spec = NULL;
2344 CamelImapMessageInfo *mi, *info;
2345 CamelStream *stream;
2349 if (store->server_level >= IMAP_LEVEL_IMAP4REV1) {
2350 if (store->headers == IMAP_FETCH_ALL_HEADERS)
2351 header_spec = g_string_new ("HEADER");
2354 header_spec = g_string_new ("HEADER.FIELDS (");
2355 header_spec = g_string_append (header_spec, CAMEL_MESSAGE_INFO_HEADERS);
2356 if (store->headers == IMAP_FETCH_MAILING_LIST_HEADERS)
2357 header_spec = g_string_append (header_spec, MAILING_LIST_HEADERS);
2358 if (store->custom_headers)
2359 header_spec = g_string_append (header_spec, store->custom_headers);
2361 temp = g_strdup(header_spec->str);
2362 temp = g_strstrip (temp);
2363 header_spec = g_string_new (temp);
2365 header_spec = g_string_append (header_spec, ")");
2368 header_spec = g_string_new ("0");
2370 d(printf("Header is : %s", header_spec->str));
2372 /* Figure out if any of the new messages are already cached (which
2373 * may be the case if we're re-syncing after disconnected operation).
2374 * If so, get their UIDs, FLAGS, and SIZEs. If not, get all that
2375 * and ask for the headers too at the same time.
2377 seq = camel_folder_summary_count (folder->summary);
2380 mi = (CamelImapMessageInfo *)camel_folder_summary_index (folder->summary, seq - 1);
2381 uidval = strtoul(camel_message_info_uid (mi), NULL, 10);
2382 camel_message_info_free(&mi->info);
2386 size = (exists - seq) * (IMAP_PRETEND_SIZEOF_FLAGS + IMAP_PRETEND_SIZEOF_SIZE + IMAP_PRETEND_SIZEOF_HEADERS);
2388 if (!camel_imap_command_start (store, folder, ex,
2389 "UID FETCH %d:* (FLAGS RFC822.SIZE INTERNALDATE BODY.PEEK[%s])",
2390 uidval + 1, header_spec->str)) {
2391 g_string_free (header_spec, TRUE);
2394 camel_operation_start (NULL, _("Fetching summary information for new messages in %s"), folder->name);
2396 /* Parse the responses. We can't add a message to the summary
2397 * until we've gotten its headers, and there's no guarantee
2398 * the server will send the responses in a useful order...
2400 fetch_data = g_ptr_array_new ();
2401 messages = g_ptr_array_new ();
2402 while ((type = camel_imap_command_response (store, &resp, ex)) ==
2403 CAMEL_IMAP_RESPONSE_UNTAGGED) {
2404 data = parse_fetch_response (imap_folder, resp);
2409 seq = GPOINTER_TO_INT (g_datalist_get_data (&data, "SEQUENCE"));
2411 g_datalist_clear (&data);
2415 if (g_datalist_get_data (&data, "FLAGS"))
2416 got += IMAP_PRETEND_SIZEOF_FLAGS;
2417 if (g_datalist_get_data (&data, "RFC822.SIZE"))
2418 got += IMAP_PRETEND_SIZEOF_SIZE;
2419 stream = g_datalist_get_data (&data, "BODY_PART_STREAM");
2421 got += IMAP_PRETEND_SIZEOF_HEADERS;
2423 /* Use the stream now so we don't tie up many
2424 * many fds if we're fetching many many messages.
2426 add_message_from_data (folder, messages, first, data);
2427 g_datalist_set_data (&data, "BODY_PART_STREAM", NULL);
2430 camel_operation_progress (NULL, got * 100 / size);
2431 g_ptr_array_add (fetch_data, data);
2433 camel_operation_end (NULL);
2435 if (type == CAMEL_IMAP_RESPONSE_ERROR)
2438 /* Free the final tagged response */
2441 /* Figure out which headers we still need to fetch. */
2442 needheaders = g_ptr_array_new ();
2444 for (i = 0; i < fetch_data->len; i++) {
2445 data = fetch_data->pdata[i];
2446 if (g_datalist_get_data (&data, "BODY_PART_LEN"))
2449 uid = g_datalist_get_data (&data, "UID");
2451 g_ptr_array_add (needheaders, uid);
2452 size += IMAP_PRETEND_SIZEOF_HEADERS;
2456 /* And fetch them */
2457 if (needheaders->len) {
2461 qsort (needheaders->pdata, needheaders->len,
2462 sizeof (void *), uid_compar);
2464 camel_operation_start (NULL, _("Fetching summary information for new messages in %s"), folder->name);
2466 while (uid < needheaders->len) {
2467 uidset = imap_uid_array_to_set (folder->summary, needheaders, uid, UID_SET_LIMIT, &uid);
2468 if (!camel_imap_command_start (store, folder, ex,
2469 "UID FETCH %s BODY.PEEK[%s]",
2470 uidset, header_spec->str)) {
2471 g_ptr_array_free (needheaders, TRUE);
2472 camel_operation_end (NULL);
2474 g_string_free (header_spec, TRUE);
2479 while ((type = camel_imap_command_response (store, &resp, ex))
2480 == CAMEL_IMAP_RESPONSE_UNTAGGED) {
2481 data = parse_fetch_response (imap_folder, resp);
2486 stream = g_datalist_get_data (&data, "BODY_PART_STREAM");
2488 add_message_from_data (folder, messages, first, data);
2489 got += IMAP_PRETEND_SIZEOF_HEADERS;
2490 camel_operation_progress (NULL, got * 100 / size);
2492 g_datalist_clear (&data);
2495 if (type == CAMEL_IMAP_RESPONSE_ERROR) {
2496 g_ptr_array_free (needheaders, TRUE);
2497 camel_operation_end (NULL);
2501 g_string_free (header_spec, TRUE);
2502 g_ptr_array_free (needheaders, TRUE);
2503 camel_operation_end (NULL);
2506 /* Now finish up summary entries (fix UIDs, set flags and size) */
2507 for (i = 0; i < fetch_data->len; i++) {
2508 data = fetch_data->pdata[i];
2510 seq = GPOINTER_TO_INT (g_datalist_get_data (&data, "SEQUENCE"));
2511 if (seq >= first + messages->len) {
2512 g_datalist_clear (&data);
2516 mi = messages->pdata[seq - first];
2518 CamelMessageInfo *pmi = NULL;
2521 /* This is a kludge around a bug in Exchange
2522 * 5.5 that sometimes claims multiple messages
2523 * have the same UID. See bug #17694 for
2524 * details. The "solution" is to create a fake
2525 * message-info with the same details as the
2526 * previously valid message. Yes, the user
2527 * will have a clone in his/her message-list,
2528 * but at least we don't crash.
2531 /* find the previous valid message info */
2532 for (j = seq - first - 1; j >= 0; j--) {
2533 pmi = messages->pdata[j];
2539 /* Server response is *really* fucked up,
2540 I guess we just pretend it never happened? */
2544 mi = (CamelImapMessageInfo *)camel_message_info_clone(pmi);
2547 uid = g_datalist_get_data (&data, "UID");
2549 mi->info.uid = g_strdup (uid);
2550 flags = GPOINTER_TO_INT (g_datalist_get_data (&data, "FLAGS"));
2552 ((CamelImapMessageInfo *)mi)->server_flags = flags;
2553 /* "or" them in with the existing flags that may
2554 * have been set by summary_info_new_from_message.
2556 mi->info.flags |= flags;
2557 flags_to_label(folder, mi);
2559 size = GPOINTER_TO_INT (g_datalist_get_data (&data, "RFC822.SIZE"));
2561 mi->info.size = size;
2563 g_datalist_clear (&data);
2565 g_ptr_array_free (fetch_data, TRUE);
2567 /* And add the entries to the summary, etc. */
2568 for (i = 0; i < messages->len; i++) {
2569 mi = messages->pdata[i];
2571 g_warning ("No information for message %d", i + first);
2572 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
2573 _("Incomplete server response: no information provided for message %d"),
2577 uid = (char *)camel_message_info_uid(mi);
2579 g_warning("Server provided no uid: message %d", i + first);
2580 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
2581 _("Incomplete server response: no UID provided for message %d"),
2585 info = (CamelImapMessageInfo *)camel_folder_summary_uid(folder->summary, uid);
2587 for (seq = 0; seq < camel_folder_summary_count (folder->summary); seq++) {
2588 if (folder->summary->messages->pdata[seq] == info)
2592 g_warning("Message already present? %s", camel_message_info_uid(mi));
2593 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
2594 _("Unexpected server response: Identical UIDs provided for messages %d and %d"),
2595 seq + 1, i + first);
2597 camel_message_info_free(&info->info);
2601 camel_folder_summary_add (folder->summary, (CamelMessageInfo *)mi);
2602 camel_folder_change_info_add_uid (changes, camel_message_info_uid (mi));
2604 if ((mi->info.flags & CAMEL_IMAP_MESSAGE_RECENT))
2605 camel_folder_change_info_recent_uid(changes, camel_message_info_uid (mi));
2608 for ( ; i < messages->len; i++) {
2609 if ((mi = messages->pdata[i]))
2610 camel_message_info_free(&mi->info);
2613 g_ptr_array_free (messages, TRUE);
2619 for (i = 0; i < fetch_data->len; i++) {
2620 data = fetch_data->pdata[i];
2621 g_datalist_clear (&data);
2623 g_ptr_array_free (fetch_data, TRUE);
2626 for (i = 0; i < messages->len; i++) {
2627 if (messages->pdata[i])
2628 camel_message_info_free(messages->pdata[i]);
2630 g_ptr_array_free (messages, TRUE);
2634 /* Called with the store's connect_lock locked */
2636 camel_imap_folder_changed (CamelFolder *folder, int exists,
2637 GArray *expunged, CamelException *ex)
2639 CamelImapFolder *imap_folder = CAMEL_IMAP_FOLDER (folder);
2640 CamelFolderChangeInfo *changes;
2641 CamelMessageInfo *info;
2644 changes = camel_folder_change_info_new ();
2648 for (i = 0; i < expunged->len; i++) {
2649 id = g_array_index (expunged, int, i);
2650 info = camel_folder_summary_index (folder->summary, id - 1);
2652 /* FIXME: danw: does this mean that the summary is corrupt? */
2653 /* I guess a message that we never retrieved got expunged? */
2657 camel_folder_change_info_remove_uid (changes, camel_message_info_uid (info));
2658 CAMEL_IMAP_FOLDER_REC_LOCK (imap_folder, cache_lock);
2659 camel_imap_message_cache_remove (imap_folder->cache, camel_message_info_uid (info));
2660 CAMEL_IMAP_FOLDER_REC_UNLOCK (imap_folder, cache_lock);
2661 camel_folder_summary_remove (folder->summary, info);
2662 camel_message_info_free(info);
2666 len = camel_folder_summary_count (folder->summary);
2668 imap_update_summary (folder, exists, changes, ex);
2670 if (camel_folder_change_info_changed (changes))
2671 camel_object_trigger_event (CAMEL_OBJECT (folder), "folder_changed", changes);
2673 camel_folder_change_info_free (changes);
2674 camel_folder_summary_save (folder->summary);
2678 imap_thaw (CamelFolder *folder)
2680 CamelImapFolder *imap_folder;
2682 CAMEL_FOLDER_CLASS (disco_folder_class)->thaw (folder);
2683 if (camel_folder_is_frozen (folder))
2686 imap_folder = CAMEL_IMAP_FOLDER (folder);
2687 if (imap_folder->need_refresh) {
2688 imap_folder->need_refresh = FALSE;
2689 imap_refresh_info (folder, NULL);
2695 camel_imap_folder_fetch_data (CamelImapFolder *imap_folder, const char *uid,
2696 const char *section_text, gboolean cache_only,
2699 CamelFolder *folder = CAMEL_FOLDER (imap_folder);
2700 CamelImapStore *store = CAMEL_IMAP_STORE (folder->parent_store);
2701 CamelImapResponse *response;
2702 CamelStream *stream;
2707 /* EXPUNGE responses have to modify the cache, which means
2708 * they have to grab the cache_lock while holding the
2711 * Because getting the service lock may cause MUCH unecessary
2712 * delay when we already have the data locally, we do the
2713 * locking separately. This could cause a race
2714 * getting the same data from the cache, but that is only
2715 * an inefficiency, and bad luck.
2717 CAMEL_IMAP_FOLDER_REC_LOCK (imap_folder, cache_lock);
2718 stream = camel_imap_message_cache_get (imap_folder->cache, uid, section_text, ex);
2719 if (!stream && (!strcmp (section_text, "HEADER") || !strcmp (section_text, "0"))) {
2720 camel_exception_clear (ex);
2721 stream = camel_imap_message_cache_get (imap_folder->cache, uid, "", ex);
2723 CAMEL_IMAP_FOLDER_REC_UNLOCK (imap_folder, cache_lock);
2725 if (stream || cache_only)
2728 camel_exception_clear(ex);
2730 CAMEL_SERVICE_REC_LOCK (store, connect_lock);
2731 CAMEL_IMAP_FOLDER_REC_LOCK (imap_folder, cache_lock);
2733 if (!camel_imap_store_connected(store, ex)) {
2734 camel_exception_set (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
2735 _("This message is not currently available"));
2736 CAMEL_IMAP_FOLDER_REC_UNLOCK (imap_folder, cache_lock);
2737 CAMEL_SERVICE_REC_UNLOCK (store, connect_lock);
2741 camel_exception_clear (ex);
2742 if (store->server_level < IMAP_LEVEL_IMAP4REV1 && !*section_text) {
2743 response = camel_imap_command (store, folder, ex,
2744 "UID FETCH %s RFC822.PEEK",
2747 response = camel_imap_command (store, folder, ex,
2748 "UID FETCH %s BODY.PEEK[%s]",
2751 /* We won't need the connect_lock again after this. */
2752 CAMEL_SERVICE_REC_UNLOCK (store, connect_lock);
2755 CAMEL_IMAP_FOLDER_REC_UNLOCK (imap_folder, cache_lock);
2759 for (i = 0; i < response->untagged->len; i++) {
2760 fetch_data = parse_fetch_response (imap_folder, response->untagged->pdata[i]);
2761 found_uid = g_datalist_get_data (&fetch_data, "UID");
2762 stream = g_datalist_get_data (&fetch_data, "BODY_PART_STREAM");
2763 if (found_uid && stream && !strcmp (uid, found_uid))
2766 g_datalist_clear (&fetch_data);
2769 camel_imap_response_free (store, response);
2770 CAMEL_IMAP_FOLDER_REC_UNLOCK (imap_folder, cache_lock);
2772 camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
2773 _("Could not find message body in FETCH response."));
2775 camel_object_ref (CAMEL_OBJECT (stream));
2776 g_datalist_clear (&fetch_data);
2783 parse_fetch_response (CamelImapFolder *imap_folder, char *response)
2786 char *start, *part_spec = NULL, *body = NULL, *uid = NULL, *idate = NULL;
2787 gboolean cache_header = TRUE, header = FALSE;
2788 size_t body_len = 0;
2790 if (*response != '(') {
2793 if (*response != '*' || *(response + 1) != ' ')
2795 seq = strtoul (response + 2, &response, 10);
2798 if (g_ascii_strncasecmp (response, " FETCH (", 8) != 0)
2802 g_datalist_set_data (&data, "SEQUENCE", GINT_TO_POINTER (seq));
2806 /* Skip the initial '(' or the ' ' between elements */
2809 if (!g_ascii_strncasecmp (response, "FLAGS ", 6)) {
2813 /* FIXME user flags */
2814 flags = imap_parse_flag_list (&response);
2816 g_datalist_set_data (&data, "FLAGS", GUINT_TO_POINTER (flags));
2817 } else if (!g_ascii_strncasecmp (response, "RFC822.SIZE ", 12)) {
2821 size = strtoul (response, &response, 10);
2822 g_datalist_set_data (&data, "RFC822.SIZE", GUINT_TO_POINTER (size));
2823 } else if (!g_ascii_strncasecmp (response, "BODY[", 5) ||
2824 !g_ascii_strncasecmp (response, "RFC822 ", 7)) {
2827 if (*response == 'B') {
2830 /* HEADER], HEADER.FIELDS (...)], or 0] */
2831 if (!g_ascii_strncasecmp (response, "HEADER", 6)) {
2833 if (!g_ascii_strncasecmp (response + 6, ".FIELDS", 7))
2834 cache_header = FALSE;
2835 } else if (!g_ascii_strncasecmp (response, "0]", 2))
2838 p = strchr (response, ']');
2839 if (!p || *(p + 1) != ' ')
2843 part_spec = g_strndup (response, p - response);
2845 part_spec = g_strdup ("HEADER.FIELDS");
2849 part_spec = g_strdup ("");
2852 if (!g_ascii_strncasecmp (response, "HEADER", 6))
2856 body = imap_parse_nstring ((const char **) &response, &body_len);
2863 body = g_strdup ("");
2864 g_datalist_set_data_full (&data, "BODY_PART_SPEC", part_spec, g_free);
2865 g_datalist_set_data_full (&data, "BODY_PART_DATA", body, g_free);
2866 g_datalist_set_data (&data, "BODY_PART_LEN", GINT_TO_POINTER (body_len));
2867 } else if (!g_ascii_strncasecmp (response, "BODY ", 5) ||
2868 !g_ascii_strncasecmp (response, "BODYSTRUCTURE ", 14)) {
2869 response = strchr (response, ' ') + 1;
2871 imap_skip_list ((const char **) &response);
2872 if (response && (response != start)) {
2873 /* To handle IMAP Server brokenness, Returning empty body, etc. See #355640 */
2874 g_datalist_set_data_full (&data, "BODY", g_strndup (start, response - start), g_free);
2876 } else if (!g_ascii_strncasecmp (response, "UID ", 4)) {
2879 len = strcspn (response + 4, " )");
2880 uid = g_strndup (response + 4, len);
2881 g_datalist_set_data_full (&data, "UID", uid, g_free);
2882 response += 4 + len;
2883 } else if (!g_ascii_strncasecmp (response, "INTERNALDATE ", 13)) {
2887 if (*response == '"') {
2889 len = strcspn (response, "\"");
2890 idate = g_strndup (response, len);
2891 g_datalist_set_data_full (&data, "INTERNALDATE", idate, g_free);
2892 response += len + 1;
2895 g_warning ("Unexpected FETCH response from server: (%s", response);
2898 } while (response && *response != ')');
2900 if (!response || *response != ')') {
2901 g_datalist_clear (&data);
2906 CamelStream *stream;
2908 if (header && !cache_header) {
2909 stream = camel_stream_mem_new_with_buffer (body, body_len);
2911 CAMEL_IMAP_FOLDER_REC_LOCK (imap_folder, cache_lock);
2912 stream = camel_imap_message_cache_insert (imap_folder->cache,
2914 body, body_len, NULL);
2915 CAMEL_IMAP_FOLDER_REC_UNLOCK (imap_folder, cache_lock);
2917 stream = camel_stream_mem_new_with_buffer (body, body_len);
2921 g_datalist_set_data_full (&data, "BODY_PART_STREAM", stream,
2922 (GDestroyNotify) camel_object_unref);