1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /* camel-imap-store.c : class for an imap store */
5 * Authors: Jeffrey Stedfast <fejj@helixcode.com>
7 * Copyright 2000 Helix Code, Inc. (www.helixcode.com)
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
33 #include <e-util/e-util.h>
35 #include "camel-imap-store.h"
36 #include "camel-imap-folder.h"
37 #include "camel-imap-utils.h"
38 #include "camel-folder.h"
39 #include "camel-exception.h"
40 #include "camel-session.h"
41 #include "camel-stream.h"
42 #include "camel-stream-buffer.h"
43 #include "camel-stream-fs.h"
44 #include "camel-url.h"
45 #include "string-utils.h"
49 /* Specified in RFC 2060 */
52 static CamelRemoteStoreClass *remote_store_class = NULL;
54 static gboolean imap_create (CamelFolder *folder, CamelException *ex);
55 static gboolean imap_connect (CamelService *service, CamelException *ex);
56 static gboolean imap_disconnect (CamelService *service, CamelException *ex);
57 static GList *query_auth_types_generic (CamelService *service, CamelException *ex);
58 static GList *query_auth_types_connected (CamelService *service, CamelException *ex);
59 static CamelFolder *get_folder (CamelStore *store, const char *folder_name, gboolean create,
61 static void imap_keepalive (CamelRemoteStore *store);
62 /*static gboolean stream_is_alive (CamelStream *istream);*/
63 static int camel_imap_status (char *cmdid, char *respbuf);
66 camel_imap_store_class_init (CamelImapStoreClass *camel_imap_store_class)
68 /* virtual method overload */
69 CamelServiceClass *camel_service_class =
70 CAMEL_SERVICE_CLASS (camel_imap_store_class);
71 CamelStoreClass *camel_store_class =
72 CAMEL_STORE_CLASS (camel_imap_store_class);
73 CamelRemoteStoreClass *camel_remote_store_class =
74 CAMEL_REMOTE_STORE_CLASS (camel_imap_store_class);
76 remote_store_class = CAMEL_REMOTE_STORE_CLASS(camel_type_get_global_classfuncs
77 (camel_remote_store_get_type ()));
79 /* virtual method overload */
80 camel_service_class->query_auth_types_generic = query_auth_types_generic;
81 camel_service_class->query_auth_types_connected = query_auth_types_connected;
82 camel_service_class->connect = imap_connect;
83 camel_service_class->disconnect = imap_disconnect;
85 camel_store_class->get_folder = get_folder;
87 camel_remote_store_class->keepalive = imap_keepalive;
91 camel_imap_store_init (gpointer object, gpointer klass)
93 CamelService *service = CAMEL_SERVICE (object);
94 CamelImapStore *imap_store = CAMEL_IMAP_STORE (object);
96 service->url_flags |= (CAMEL_SERVICE_URL_NEED_USER |
97 CAMEL_SERVICE_URL_NEED_HOST |
98 CAMEL_SERVICE_URL_ALLOW_PATH |
99 CAMEL_SERVICE_URL_ALLOW_AUTH);
101 imap_store->dir_sep = g_strdup ("/"); /*default*/
102 imap_store->current_folder = NULL;
106 camel_imap_store_get_type (void)
108 static CamelType camel_imap_store_type = CAMEL_INVALID_TYPE;
110 if (camel_imap_store_type == CAMEL_INVALID_TYPE) {
111 camel_imap_store_type =
112 camel_type_register (CAMEL_REMOTE_STORE_TYPE, "CamelImapStore",
113 sizeof (CamelImapStore),
114 sizeof (CamelImapStoreClass),
115 (CamelObjectClassInitFunc) camel_imap_store_class_init,
117 (CamelObjectInitFunc) camel_imap_store_init,
118 (CamelObjectFinalizeFunc) NULL);
121 return camel_imap_store_type;
124 static CamelServiceAuthType password_authtype = {
127 "This option will connect to the IMAP server using a "
128 "plaintext password.",
135 query_auth_types_connected (CamelService *service, CamelException *ex)
139 gboolean passwd = TRUE;
142 passwd = try_connect (service, ex);
143 if (camel_exception_get_id (ex) != CAMEL_EXCEPTION_NONE)
148 ret = g_list_append (ret, &password_authtype);
151 camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
152 "Could not connect to IMAP server on %s.",
153 service->url->host ? service->url->host :
159 g_warning ("imap::query_auth_types_connected: not implemented. Defaulting.");
160 /* FIXME: use the classfunc instead of the local? */
161 return query_auth_types_generic (service, ex);
166 query_auth_types_generic (CamelService *service, CamelException *ex)
170 prev = CAMEL_SERVICE_CLASS (remote_store_class)->query_auth_types_generic (service, ex);
171 return g_list_prepend (prev, &password_authtype);
175 imap_connect (CamelService *service, CamelException *ex)
177 CamelImapStore *store = CAMEL_IMAP_STORE (service);
178 CamelSession *session = camel_service_get_session (CAMEL_SERVICE (store));
179 gchar *buf, *result, *errbuf = NULL;
180 gboolean authenticated = FALSE;
183 if (CAMEL_SERVICE_CLASS (remote_store_class)->connect (service, ex) == FALSE)
187 g_free (store->dir_sep);
188 store->dir_sep = g_strdup ("/"); /* default dir sep */
190 /* Read the greeting, if any. */
191 if (camel_remote_store_recv_line (CAMEL_REMOTE_STORE (service), &buf, ex) < 0) {
196 /* authenticate the user */
197 while (!authenticated) {
199 /* We need to un-cache the password before prompting again */
200 camel_session_query_authenticator (session,
201 CAMEL_AUTHENTICATOR_TELL, NULL,
202 TRUE, service, "password", ex);
203 g_free (service->url->passwd);
204 service->url->passwd = NULL;
207 if (!service->url->authmech && !service->url->passwd) {
210 prompt = g_strdup_printf ("%sPlease enter the IMAP password for %s@%s",
211 errbuf ? errbuf : "", service->url->user, service->url->host);
212 service->url->passwd =
213 camel_session_query_authenticator (session,
214 CAMEL_AUTHENTICATOR_ASK, prompt,
215 TRUE, service, "password", ex);
220 if (!service->url->passwd) {
221 camel_exception_set (ex, CAMEL_EXCEPTION_USER_CANCEL,
222 "You didn\'t enter a password.");
227 status = camel_imap_command (store, NULL, ex, "LOGIN \"%s\" \"%s\"",
229 service->url->passwd);
231 if (status != CAMEL_IMAP_OK) {
232 errbuf = g_strdup_printf ("Unable to authenticate to IMAP server.\n"
234 camel_exception_get_description (ex));
235 camel_exception_clear (ex);
237 g_message ("IMAP Service sucessfully authenticated user %s", service->url->user);
238 authenticated = TRUE;
242 /* Now lets find out the IMAP capabilities */
243 status = camel_imap_command_extended (store, NULL, &result, ex, "CAPABILITY");
245 if (status != CAMEL_IMAP_OK) {
246 /* Non-fatal error... (ex is set) */
249 /* parse for capabilities here. */
250 if (e_strstrcase (result, "IMAP4REV1"))
251 store->server_level = IMAP_LEVEL_IMAP4REV1;
252 else if (e_strstrcase (result, "IMAP4"))
253 store->server_level = IMAP_LEVEL_IMAP4;
255 store->server_level = IMAP_LEVEL_UNKNOWN;
257 if ((store->server_level >= IMAP_LEVEL_IMAP4REV1) || (e_strstrcase (result, "STATUS")))
258 store->has_status_capability = TRUE;
260 store->has_status_capability = FALSE;
264 /* We now need to find out which directory separator this daemon uses */
265 status = camel_imap_command_extended (store, NULL, &result, ex, "LIST \"\" \"\"");
267 if (status != CAMEL_IMAP_OK) {
268 /* Again, this is non-fatal */
270 char *flags, *sep, *folder;
272 if (imap_parse_list_response (result, "", &flags, &sep, &folder)) {
274 g_free (store->dir_sep);
275 store->dir_sep = g_strdup (sep);
286 camel_remote_store_refresh_folders (CAMEL_REMOTE_STORE (store), ex);
288 return ! camel_exception_is_set (ex);
292 imap_disconnect (CamelService *service, CamelException *ex)
294 CamelImapStore *store = CAMEL_IMAP_STORE (service);
298 /* send the logout command */
299 status = camel_imap_command_extended (store, NULL, &result, ex, "LOGOUT");
300 if (status != CAMEL_IMAP_OK) {
301 /* Oh fuck it, we're disconnecting anyway... */
305 g_free (store->dir_sep);
306 store->dir_sep = NULL;
308 store->current_folder = NULL;
310 return CAMEL_SERVICE_CLASS (remote_store_class)->disconnect (service, ex);
314 camel_imap_store_get_toplevel_dir (CamelImapStore *store)
316 CamelURL *url = CAMEL_SERVICE (store)->url;
318 g_assert (url != NULL);
323 imap_folder_exists (CamelFolder *folder, gboolean *selectable, CamelException *ex)
325 CamelStore *store = CAMEL_STORE (folder->parent_store);
326 CamelURL *url = CAMEL_SERVICE (store)->url;
327 gchar *result, *folder_path, *dir_sep;
328 char *flags, *sep, *dirname;
331 dir_sep = CAMEL_IMAP_STORE (folder->parent_store)->dir_sep;
333 g_return_val_if_fail (dir_sep, FALSE);
335 if (url && url->path && *(url->path + 1) && g_strcasecmp (folder->full_name, "INBOX"))
336 folder_path = g_strdup_printf ("%s%s%s", url->path + 1, dir_sep, folder->full_name);
338 folder_path = g_strdup (folder->full_name);
340 if (!g_strcasecmp (folder_path, "INBOX")) {
341 g_free (folder_path);
347 /* it's always gonna be FALSE unless it's true - how's that for a comment? ;-) */
351 status = camel_imap_command_extended (CAMEL_IMAP_STORE (store), NULL,
352 &result, ex, "LIST \"\" %s", folder_path);
354 g_free (folder_path);
356 if (status != CAMEL_IMAP_OK) {
361 if (imap_parse_list_response (result, "", &flags, &sep, &dirname)) {
363 *selectable = !e_strstrcase (flags, "NoSelect");
381 imap_folder_exists (CamelFolder *folder, CamelException *ex)
383 CamelStore *store = CAMEL_STORE (folder->parent_store);
384 CamelURL *url = CAMEL_SERVICE (store)->url;
385 gchar *result, *folder_path, *dir_sep;
388 dir_sep = CAMEL_IMAP_STORE (folder->parent_store)->dir_sep;
390 g_return_val_if_fail (dir_sep, FALSE);
392 if (url && url->path && *(url->path + 1) && g_strcasecmp (folder->full_name, "INBOX"))
393 folder_path = g_strdup_printf ("%s%s%s", url->path + 1, dir_sep, folder->full_name);
395 folder_path = g_strdup (folder->full_name);
397 status = camel_imap_command_extended (CAMEL_IMAP_STORE (folder->parent_store), NULL,
398 &result, ex, "EXAMINE %s", folder_path);
400 if (status != CAMEL_IMAP_OK) {
401 g_free (folder_path);
404 g_free (folder_path);
412 imap_create (CamelFolder *folder, CamelException *ex)
414 CamelStore *store = CAMEL_STORE (folder->parent_store);
415 CamelURL *url = CAMEL_SERVICE (store)->url;
416 gchar *result, *folder_path, *dir_sep;
419 g_return_val_if_fail (folder != NULL, FALSE);
421 if (!(folder->full_name || folder->name)) {
422 camel_exception_set (ex, CAMEL_EXCEPTION_FOLDER_INVALID,
423 "invalid folder path. Use set_name ?");
427 if (!g_strcasecmp (folder->full_name, "INBOX"))
430 if (imap_folder_exists (folder, NULL, ex))
433 /* create the directory for the subfolder */
434 dir_sep = CAMEL_IMAP_STORE (folder->parent_store)->dir_sep;
436 g_return_val_if_fail (dir_sep, FALSE);
438 if (url && url->path && *(url->path + 1) && g_strcasecmp (folder->full_name, "INBOX"))
439 folder_path = g_strdup_printf ("%s%s%s", url->path + 1, dir_sep, folder->full_name);
441 folder_path = g_strdup (folder->full_name);
443 status = camel_imap_command_extended (CAMEL_IMAP_STORE (folder->parent_store), NULL,
444 &result, ex, "CREATE %s", folder_path);
445 g_free (folder_path);
447 if (status != CAMEL_IMAP_OK)
455 folder_is_selectable (CamelStore *store, const char *folder_path, CamelException *ex)
457 char *result, *flags, *sep, *folder;
460 if (!g_strcasecmp (folder_path, "INBOX"))
463 status = camel_imap_command_extended (CAMEL_IMAP_STORE (store), NULL,
464 &result, ex, "LIST \"\" %s", folder_path);
465 if (status != CAMEL_IMAP_OK)
468 if (imap_parse_list_response (result, "", &flags, &sep, &folder)) {
471 retval = !e_strstrcase (flags, "NoSelect");
487 get_folder (CamelStore *store, const char *folder_name, gboolean create, CamelException *ex)
489 CamelURL *url = CAMEL_SERVICE (store)->url;
490 CamelFolder *new_folder;
491 char *folder_path, *dir_sep;
492 gboolean exists = FALSE;
495 g_return_val_if_fail (store != NULL, NULL);
496 g_return_val_if_fail (folder_name != NULL, NULL);
498 dir_sep = CAMEL_IMAP_STORE (store)->dir_sep;
500 /* if we're trying to get the top-level dir, we really want the namespace */
501 if (!dir_sep || !strcmp (folder_name, dir_sep))
502 folder_path = g_strdup (url->path + 1);
504 folder_path = g_strdup (folder_name);
506 new_folder = camel_imap_folder_new (store, folder_path, ex);
508 if (camel_exception_is_set (ex))
511 /* this is the top-level dir, we already know it exists - it has to! */
512 if (!strcmp (folder_name, dir_sep)) {
513 camel_folder_refresh_info (new_folder, ex);
517 if (imap_folder_exists (new_folder, &selectable, ex)) {
518 /* ah huh, so the folder *does* exist... */
521 /* now lets see if it's selectable... */
523 camel_exception_clear (ex);
524 new_folder->can_hold_messages = FALSE;
528 if (!exists && create && !imap_create (new_folder, ex)) {
529 g_free (folder_path);
530 camel_object_unref (CAMEL_OBJECT (new_folder));
534 /* this is where we *should refresh_info, not in imap_folder_new() */
535 camel_folder_refresh_info (new_folder, ex);
541 imap_keepalive (CamelRemoteStore *store)
543 CamelImapStore *imap_store = CAMEL_IMAP_STORE (store);
548 camel_exception_init (&ex);
549 status = camel_imap_command_extended (imap_store, imap_store->current_folder,
550 &result, &ex, "NOOP");
551 camel_exception_clear (&ex);
557 stream_is_alive (CamelStream *istream)
559 CamelStreamFs *fs_stream;
562 g_return_val_if_fail (istream != NULL, FALSE);
564 fs_stream = CAMEL_STREAM_FS (CAMEL_STREAM_BUFFER (istream)->stream);
565 g_return_val_if_fail (fs_stream->fd != -1, FALSE);
567 if (read (fs_stream->fd, (void *) &buf, 0) == 0)
575 camel_imap_status (char *cmdid, char *respbuf)
580 if (!strncmp (respbuf, cmdid, strlen (cmdid))) {
581 retcode = imap_next_word (respbuf);
583 if (!strncmp (retcode, "OK", 2))
584 return CAMEL_IMAP_OK;
585 else if (!strncmp (retcode, "NO", 2))
586 return CAMEL_IMAP_NO;
587 else if (!strncmp (retcode, "BAD", 3))
588 return CAMEL_IMAP_BAD;
592 return CAMEL_IMAP_FAIL;
596 check_current_folder (CamelImapStore *store, CamelFolder *folder, char *fmt, CamelException *ex)
598 CamelURL *url = CAMEL_SERVICE (store)->url;
599 char *result, *folder_path, *dir_sep;
602 /* return OK if we meet one of the following criteria:
603 * 1. the command doesn't care about which folder we're in (folder == NULL)
604 * 2. if we're already in the right folder (store->current_folder == folder)
605 * 3. we're going to create a new folder */
606 if (!folder || store->current_folder == folder || !strncmp (fmt, "CREATE ", 7))
607 return CAMEL_IMAP_OK;
609 dir_sep = store->dir_sep;
611 if (url && url->path && *(url->path + 1) && g_strcasecmp (folder->full_name, "INBOX"))
612 folder_path = g_strdup_printf ("%s%s%s", url->path + 1, dir_sep, folder->full_name);
614 folder_path = g_strdup (folder->full_name);
616 status = camel_imap_command_extended (store, NULL, &result, ex, "SELECT %s", folder_path);
617 g_free (folder_path);
619 if (!result || status != CAMEL_IMAP_OK) {
620 store->current_folder = NULL;
625 /* remember our currently selected folder */
626 store->current_folder = folder;
628 return CAMEL_IMAP_OK;
632 send_command (CamelImapStore *store, char **cmdid, char *fmt, va_list ap, CamelException *ex)
636 *cmdid = g_strdup_printf ("A%.5d", store->command++);
638 cmdbuf = g_strdup_vprintf (fmt, ap);
640 if (camel_remote_store_send_string (CAMEL_REMOTE_STORE (store), ex, "%s %s\r\n", *cmdid, cmdbuf) < 0) {
653 * camel_imap_command: Send a command to a IMAP server.
654 * @store: the IMAP store
655 * @folder: The folder to perform the operation in
656 * @ret: a pointer to return the full server response in
657 * @ex: a CamelException.
658 * @fmt: a printf-style format string, followed by arguments
660 * This camel method sends the command specified by @fmt and the following
661 * arguments to the connected IMAP store specified by @store. It then
662 * reads the server's response and parses out the status code. If
663 * the caller passed a non-NULL pointer for @ret, camel_imap_command
664 * will set it to point to a buffer containing the rest of the
665 * response from the IMAP server. (If @ret was passed but there was
666 * no extended response, @ret will be set to NULL.) The caller function is
667 * responsible for freeing @ret.
669 * Return value: one of CAMEL_IMAP_OK (command executed successfully),
670 * CAMEL_IMAP_NO (operational error message), CAMEL_IMAP_BAD (error
671 * message from the server), or CAMEL_IMAP_FAIL (a protocol-level error
672 * occurred, and Camel is uncertain of the result of the command.)
675 camel_imap_command (CamelImapStore *store, CamelFolder *folder, CamelException *ex, char *fmt, ...)
677 char *cmdid, *respbuf, *word;
678 gint status = CAMEL_IMAP_OK;
681 /* check for current folder */
682 status = check_current_folder (store, folder, fmt, ex);
683 if (status != CAMEL_IMAP_OK)
686 /* send the command */
688 if (!send_command (store, &cmdid, fmt, ap, ex)) {
691 return CAMEL_IMAP_FAIL;
695 /* read single line response */
696 if (camel_remote_store_recv_line (CAMEL_REMOTE_STORE (store), &respbuf, ex) < 0) {
698 return CAMEL_IMAP_FAIL;
701 status = camel_imap_status (cmdid, respbuf);
704 if (status == CAMEL_IMAP_OK)
708 /* get error response and set exception accordingly */
709 word = imap_next_word (respbuf); /* points to status */
710 word = imap_next_word (word); /* points to fail message, if there is one */
712 camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
713 "IMAP command failed: %s", word);
715 camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
716 "IMAP command failed: %s", "Unknown");
723 * camel_imap_command_extended: Send a command to a IMAP server and get
724 * a multi-line response.
725 * @store: the IMAP store
726 * @folder: The folder to perform the operation in
727 * @ret: a pointer to return the full server response in
728 * @fmt: a printf-style format string, followed by arguments
730 * This camel method sends the IMAP command specified by @fmt and the
731 * following arguments to the IMAP store specified by @store. If the
732 * store is in a disconnected state, camel_imap_command_extended will first
733 * re-connect the store before sending the specified IMAP command. It then
734 * reads the server's response and parses out the status code. If the caller
735 * passed a non-NULL pointer for @ret, camel_imap_command_extended will set
736 * it to point to a buffer containing the rest of the response from the IMAP
737 * server. (If @ret was passed but there was no extended response, @ret will
738 * be set to NULL.) The caller function is responsible for freeing @ret.
740 * This camel method gets the additional data returned by "multi-line" IMAP
741 * commands, such as SELECT, LIST, and various other commands.
742 * The returned data is un-byte-stuffed, and has lines termined by
743 * newlines rather than CR/LF pairs.
745 * Return value: one of CAMEL_IMAP_OK (command executed successfully),
746 * CAMEL_IMAP_NO (operational error message), CAMEL_IMAP_BAD (error
747 * message from the server), or CAMEL_IMAP_FAIL (a protocol-level error
748 * occurred, and Camel is uncertain of the result of the command.)
752 camel_imap_command_extended (CamelImapStore *store, CamelFolder *folder, char **ret, CamelException *ex, char *fmt, ...)
754 gint status = CAMEL_IMAP_OK;
755 GPtrArray *data, *expunged;
756 gchar *respbuf, *cmdid;
762 /* check for current folder */
763 status = check_current_folder (store, folder, fmt, ex);
764 if (status != CAMEL_IMAP_OK)
767 /* send the command */
769 if (!send_command (store, &cmdid, fmt, ap, ex)) {
771 return CAMEL_IMAP_FAIL;
775 data = g_ptr_array_new ();
776 expunged = g_ptr_array_new ();
778 /* read multi-line response */
780 if (camel_remote_store_recv_line (CAMEL_REMOTE_STORE (store), &respbuf, ex) < 0) {
782 for (i = 0; i < data->len; i++)
783 g_free (data->pdata[i]);
784 g_ptr_array_free (data, TRUE);
786 for (i = 0; i < expunged->len; i++)
787 g_free (expunged->pdata[i]);
788 g_ptr_array_free (expunged, TRUE);
790 return CAMEL_IMAP_FAIL;
793 g_ptr_array_add (data, respbuf);
794 len += strlen (respbuf) + 1;
796 /* IMAPs multi-line response ends with the cmdid string at the beginning of the line */
797 if (!strncmp (respbuf, cmdid, strlen (cmdid))) {
798 status = camel_imap_status (cmdid, respbuf);
802 /* Check for a RECENT in the untagged response */
803 if (*respbuf == '*') {
804 if (strstr (respbuf, "RECENT")) {
807 d(fprintf (stderr, "*** We may have found a 'RECENT' flag: %s\n", respbuf));
808 /* Make sure it's in the form: "* %d RECENT" */
809 rcnt = imap_next_word (respbuf);
810 if (*rcnt >= '0' && *rcnt <= '9' && !strncmp ("RECENT", imap_next_word (rcnt), 6))
811 recent = atoi (rcnt);
812 } else if (strstr (respbuf, "EXPUNGE")) {
816 d(fprintf (stderr, "*** We may have found an 'EXPUNGE' flag: %s\n", respbuf));
817 /* Make sure it's in the form: "* %d EXPUNGE" */
818 id_str = imap_next_word (respbuf);
819 if (*id_str >= '0' && *id_str <= '9' && !strncmp ("EXPUNGE", imap_next_word (id_str), 7)) {
821 g_ptr_array_add (expunged, g_strdup_printf ("%d", id));
827 if (status == CAMEL_IMAP_OK) {
830 /* populate the return buffer with the server response */
831 *ret = g_new (char, len + 1);
834 for (i = 0; i < data->len; i++) {
837 datap = (char *) data->pdata[i];
840 len = strlen (datap);
841 memcpy (p, datap, len);
852 word = imap_next_word (respbuf); /* should now point to status */
854 word = imap_next_word (word); /* points to fail message, if there is one */
856 camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
857 "IMAP command failed: %s", word);
859 camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
860 "IMAP command failed: Unknown");
866 /* Update the summary */
867 if (folder && (recent > 0 || expunged->len > 0)) {
870 camel_exception_init (&dex);
871 camel_imap_folder_changed (folder, recent, expunged, &dex);
872 camel_exception_clear (&dex);
875 for (i = 0; i < data->len; i++)
876 g_free (data->pdata[i]);
877 g_ptr_array_free (data, TRUE);
879 for (i = 0; i < expunged->len; i++)
880 g_free (expunged->pdata[i]);
881 g_ptr_array_free (expunged, TRUE);
887 * camel_imap_fetch_command: Send a FETCH request to an IMAP server and get
888 * a multi-line response.
889 * @store: the IMAP store
890 * @folder: The folder to perform the operation in
891 * @ret: a pointer to return the full server response in
892 * @fmt: a printf-style format string, followed by arguments
894 * This camel method sends the IMAP FETCH command specified by @fmt and the
895 * following arguments to the IMAP store specified by @store. If the
896 * store is in a disconnected state, camel_imap_fetch_command will first
897 * re-connect the store before sending the specified IMAP command. It then
898 * reads the server's response and parses out the status code. If the caller
899 * passed a non-NULL pointer for @ret, camel_imap_fetch_command will set
900 * it to point to a buffer containing the rest of the response from the IMAP
901 * server. (If @ret was passed but there was no extended response, @ret will
902 * be set to NULL.) The caller function is responsible for freeing @ret.
904 * Return value: one of CAMEL_IMAP_OK (command executed successfully),
905 * CAMEL_IMAP_NO (operational error message), CAMEL_IMAP_BAD (error
906 * message from the server), or CAMEL_IMAP_FAIL (a protocol-level error
907 * occurred, and Camel is uncertain of the result of the command.)
911 camel_imap_fetch_command (CamelImapStore *store, CamelFolder *folder, char **ret, CamelException *ex, char *fmt, ...)
913 /* Security Note: We have to be careful about assuming
914 * that a server response is valid as the command we are
915 * calling may require a literal string response which could
916 * possibly contain strings that appear to be valid server
917 * responses but aren't. We should, therefor, find a way to
918 * determine whether we are actually reading server responses.
920 gint status = CAMEL_IMAP_OK;
921 GPtrArray *data, *expunged;
922 gboolean is_notification;
923 gchar *respbuf, *cmdid;
930 status = check_current_folder (store, folder, fmt, ex);
931 if (status != CAMEL_IMAP_OK)
934 /* send the command */
936 if (!send_command (store, &cmdid, fmt, ap, ex)) {
938 return CAMEL_IMAP_FAIL;
942 data = g_ptr_array_new ();
944 /* get first response line */
945 if (camel_remote_store_recv_line (CAMEL_REMOTE_STORE (store), &respbuf, ex) != -1) {
948 g_ptr_array_add (data, respbuf);
949 len += strlen (respbuf) + 1;
951 for (p = respbuf; *p && *p != '{' && *p != '"' && *p != '\n'; p++);
954 /* a quoted string - section 4.3 */
956 for (q = p; *q && *q != '"'; q++);
957 partlen = (guint32) (q - p);
959 is_notification = TRUE;
963 /* a literal string - section 4.3 */
964 partlen = atoi (p + 1);
966 /* add len to partlen because the partlen
967 doesn't count the first response buffer */
970 is_notification = FALSE;
975 g_ptr_array_free (data, TRUE);
976 return CAMEL_IMAP_FAIL;
979 g_ptr_array_free (data, TRUE);
980 return CAMEL_IMAP_FAIL;
983 expunged = g_ptr_array_new ();
985 /* read multi-line response */
987 if (camel_remote_store_recv_line (CAMEL_REMOTE_STORE (store), &respbuf, ex) < 0) {
989 for (i = 0; i < data->len; i++)
990 g_free (data->pdata[i]);
991 g_ptr_array_free (data, TRUE);
993 for (i = 0; i < expunged->len; i++)
994 g_free (expunged->pdata[i]);
995 g_ptr_array_free (expunged, TRUE);
997 return CAMEL_IMAP_FAIL;
1000 g_ptr_array_add (data, respbuf);
1001 len += strlen (respbuf) + 1;
1003 /* IMAPs multi-line response ends with the cmdid string at the beginning of the line */
1004 if (is_notification && !strncmp (respbuf, cmdid, strlen (cmdid))) {
1005 status = camel_imap_status (cmdid, respbuf);
1009 /* FIXME: this is redundant */
1010 /* If recent or expunge flags were somehow set and this
1011 response doesn't begin with a '*' then
1012 recent/expunged must have been misdetected */
1013 if ((recent || expunged->len > 0) && *respbuf != '*') {
1014 d(fprintf (stderr, "hmmm, someone tried to pull a fast one on us.\n"));
1018 for (i = 0; i < expunged->len; i++) {
1019 g_free (expunged->pdata[i]);
1020 g_ptr_array_remove_index (expunged, i);
1024 /* Check for a RECENT in the untagged response */
1025 if (*respbuf == '*' && is_notification) {
1026 if (strstr (respbuf, "RECENT")) {
1029 d(fprintf (stderr, "*** We may have found a 'RECENT' flag: %s\n", respbuf));
1030 /* Make sure it's in the form: "* %d RECENT" */
1031 rcnt = imap_next_word (respbuf);
1032 if (*rcnt >= '0' && *rcnt <= '9' && !strncmp ("RECENT", imap_next_word (rcnt), 6))
1033 recent = atoi (rcnt);
1034 } else if (strstr (respbuf, "EXPUNGE")) {
1038 d(fprintf (stderr, "*** We may have found an 'EXPUNGE' flag: %s\n", respbuf));
1039 /* Make sure it's in the form: "* %d EXPUNGE" */
1040 id_str = imap_next_word (respbuf);
1041 if (*id_str >= '0' && *id_str <= '9' && !strncmp ("EXPUNGE", imap_next_word (id_str), 7)) {
1043 g_ptr_array_add (expunged, g_strdup_printf ("%d", id));
1048 if (!is_notification) {
1051 is_notification = TRUE;
1055 if (status == CAMEL_IMAP_OK) {
1058 /* populate the return buffer with the server response */
1059 *ret = g_new (char, len + 1);
1062 for (i = 0; i < data->len; i++) {
1065 datap = (char *) data->pdata[i];
1068 len = strlen (datap);
1069 memcpy (p, datap, len);
1076 /* command failed */
1080 word = imap_next_word (respbuf); /* should now point to status */
1082 word = imap_next_word (word); /* points to fail message, if there is one */
1084 camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
1085 "IMAP command failed: %s", word);
1087 camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
1088 "IMAP command failed: Unknown");
1094 /* Update the summary */
1095 if (folder && (recent > 0 || expunged->len > 0)) {
1098 camel_exception_init (&dex);
1099 camel_imap_folder_changed (folder, recent, expunged, &dex);
1100 camel_exception_clear (&dex);
1103 for (i = 0; i < data->len; i++)
1104 g_free (data->pdata[i]);
1105 g_ptr_array_free (data, TRUE);
1107 for (i = 0; i < expunged->len; i++)
1108 g_free (expunged->pdata[i]);
1109 g_ptr_array_free (expunged, TRUE);
1115 * camel_imap_command_preliminary: Send a preliminary command to the
1117 * @store: the IMAP store
1118 * @cmdid: a pointer to return the command identifier (for use in
1119 * camel_imap_command_continuation)
1120 * @fmt: a printf-style format string, followed by arguments
1122 * This camel method sends a preliminary IMAP command specified by
1123 * @fmt and the following arguments to the IMAP store specified by
1124 * @store. This function is meant for use with multi-transactional
1125 * IMAP communications like Kerberos authentication and APPEND.
1127 * If the caller passed a non-NULL pointer for @ret,
1128 * camel_imap_command_preliminary will set it to point to a buffer
1129 * containing the rest of the response from the IMAP server. The
1130 * caller function is responsible for freeing @ret.
1132 * Return value: one of CAMEL_IMAP_PLUS, CAMEL_IMAP_NO, CAMEL_IMAP_BAD
1133 * or CAMEL_IMAP_FAIL
1135 * Note: on success (CAMEL_IMAP_PLUS), you will need to follow up with
1136 * a camel_imap_command_continuation call.
1139 camel_imap_command_preliminary (CamelImapStore *store, char **cmdid, CamelException *ex, char *fmt, ...)
1141 char *respbuf, *word;
1142 gint status = CAMEL_IMAP_OK;
1145 /* send the command */
1147 if (!send_command (store, cmdid, fmt, ap, ex)) {
1149 return CAMEL_IMAP_FAIL;
1153 /* read single line response */
1154 if (camel_remote_store_recv_line (CAMEL_REMOTE_STORE (store), &respbuf, ex) < 0)
1155 return CAMEL_IMAP_FAIL;
1157 /* Check for '+' which indicates server is ready for command continuation */
1158 if (*respbuf == '+') {
1160 return CAMEL_IMAP_PLUS;
1163 status = camel_imap_status (*cmdid, respbuf);
1167 /* get error response and set exception accordingly */
1168 word = imap_next_word (respbuf); /* points to status */
1169 word = imap_next_word (word); /* points to fail message, if there is one */
1171 camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
1172 "IMAP command failed: %s", word);
1174 camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
1175 "IMAP command failed: %s", "Unknown");
1182 * camel_imap_command_continuation: Handle another transaction with the IMAP
1183 * server and possibly get a multi-line response.
1184 * @store: the IMAP store
1185 * @cmdid: The command identifier returned from camel_imap_command_preliminary
1186 * @ret: a pointer to return the full server response in
1187 * @cmdbuf: buffer containing the response/request data
1189 * This method is for sending continuing responses to the IMAP server. Meant
1190 * to be used as a followup to camel_imap_command_preliminary.
1191 * camel_imap_command_continuation will set @ret to point to a buffer
1192 * containing the rest of the response from the IMAP server. The
1193 * caller function is responsible for freeing @ret.
1195 * Return value: one of CAMEL_IMAP_PLUS (command requires additional data),
1196 * CAMEL_IMAP_OK (command executed successfully),
1197 * CAMEL_IMAP_NO (operational error message),
1198 * CAMEL_IMAP_BAD (error message from the server), or
1199 * CAMEL_IMAP_FAIL (a protocol-level error occurred, and Camel is uncertain
1200 * of the result of the command.)
1203 camel_imap_command_continuation (CamelImapStore *store, char **ret, char *cmdid, char *cmdbuf, CamelException *ex)
1205 gint status = CAMEL_IMAP_OK;
1211 if (camel_remote_store_send_string (CAMEL_REMOTE_STORE (store), ex, "%s\r\n", cmdbuf) < 0) {
1214 return CAMEL_IMAP_FAIL;
1217 data = g_ptr_array_new ();
1219 /* read multi-line response */
1221 if (camel_remote_store_recv_line (CAMEL_REMOTE_STORE (store), &respbuf, ex) < 0) {
1223 for (i = 0; i < data->len; i++)
1224 g_free (data->pdata[i]);
1225 g_ptr_array_free (data, TRUE);
1227 return CAMEL_IMAP_FAIL;
1230 g_ptr_array_add (data, respbuf);
1231 len += strlen (respbuf) + 1;
1233 /* IMAPs multi-line response ends with the cmdid string at the beginning of the line */
1234 if (!strncmp (respbuf, cmdid, strlen (cmdid))) {
1235 status = camel_imap_status (cmdid, respbuf);
1240 if (status == CAMEL_IMAP_OK) {
1243 /* populate the return buffer with the server response */
1244 *ret = g_new (char, len + 1);
1247 for (i = 0; i < data->len; i++) {
1250 datap = (char *) data->pdata[i];
1253 len = strlen (datap);
1254 memcpy (p, datap, len);
1261 /* command failed */
1265 word = imap_next_word (respbuf); /* should now point to status */
1267 word = imap_next_word (word); /* points to fail message, if there is one */
1269 camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
1270 "IMAP command failed: %s", word);
1272 camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
1273 "IMAP command failed: Unknown");
1280 for (i = 0; i < data->len; i++)
1281 g_free (data->pdata[i]);
1282 g_ptr_array_free (data, TRUE);
1288 * camel_imap_command_continuation_with_stream: Handle another transaction with the IMAP
1289 * server and possibly get a multi-line response.
1290 * @store: the IMAP store
1291 * @cmdid: The command identifier returned from camel_imap_command_preliminary
1292 * @ret: a pointer to return the full server response in
1293 * @cstream: a CamelStream containing a continuation response.
1295 * This method is for sending continuing responses to the IMAP server. Meant
1296 * to be used as a followup to camel_imap_command_preliminary.
1297 * camel_imap_command_continuation will set @ret to point to a buffer
1298 * containing the rest of the response from the IMAP server. The
1299 * caller function is responsible for freeing @ret.
1301 * Return value: one of CAMEL_IMAP_PLUS (command requires additional data),
1302 * CAMEL_IMAP_OK (command executed successfully),
1303 * CAMEL_IMAP_NO (operational error message),
1304 * CAMEL_IMAP_BAD (error message from the server), or
1305 * CAMEL_IMAP_FAIL (a protocol-level error occurred, and Camel is uncertain
1306 * of the result of the command.)
1309 camel_imap_command_continuation_with_stream (CamelImapStore *store, char **ret, char *cmdid,
1310 CamelStream *cstream, CamelException *ex)
1312 gint status = CAMEL_IMAP_OK;
1319 if (camel_remote_store_send_stream (CAMEL_REMOTE_STORE (store), cstream, ex) < 0) {
1322 return CAMEL_IMAP_FAIL;
1325 data = g_ptr_array_new ();
1327 /* read the servers multi-line response */
1329 if (camel_remote_store_recv_line (CAMEL_REMOTE_STORE (store), &respbuf, ex) < 0) {
1331 for (i = 0; i < data->len; i++)
1332 g_free (data->pdata[i]);
1333 g_ptr_array_free (data, TRUE);
1335 return CAMEL_IMAP_FAIL;
1338 g_ptr_array_add (data, respbuf);
1339 len += strlen (respbuf) + 1;
1341 /* IMAPs multi-line response ends with the cmdid string at the beginning of the line */
1342 if (!strncmp (respbuf, cmdid, strlen (cmdid))) {
1343 status = camel_imap_status (cmdid, respbuf);
1348 if (status == CAMEL_IMAP_OK) {
1351 /* populate the return buffer with the server response */
1352 *ret = g_new (char, len + 1);
1355 for (i = 0; i < data->len; i++) {
1358 datap = (char *) data->pdata[i];
1361 len = strlen (datap);
1362 memcpy (p, datap, len);
1369 /* command failed */
1373 word = imap_next_word (respbuf); /* should now point to status */
1375 word = imap_next_word (word); /* points to fail message, if there is one */
1377 camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
1378 "IMAP command failed: %s", word);
1380 camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
1381 "IMAP command failed: Unknown error");
1388 for (i = 0; i < data->len; i++)
1389 g_free (data->pdata[i]);
1390 g_ptr_array_free (data, TRUE);