209b8a6fbb81b95b13b8d46c54190b471d7ad5eb
[platform/upstream/evolution-data-server.git] / camel / providers / imap / camel-imap-store.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /* camel-imap-store.c : class for an imap store */
3
4 /*
5  *  Authors: Jeffrey Stedfast <fejj@helixcode.com>
6  *
7  *  Copyright 2000 Helix Code, Inc. (www.helixcode.com)
8  *
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.
13  *
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.
18  *
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.
22  *
23  */
24
25
26 #include <config.h>
27
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <unistd.h>
32
33 #include <e-util/e-util.h>
34
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"
46
47 #define d(x) x
48
49 /* Specified in RFC 2060 */
50 #define IMAP_PORT 143
51
52 static CamelRemoteStoreClass *remote_store_class = NULL;
53
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,
60                                 CamelException *ex);
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);
64
65 static void
66 camel_imap_store_class_init (CamelImapStoreClass *camel_imap_store_class)
67 {
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);
75         
76         remote_store_class = CAMEL_REMOTE_STORE_CLASS(camel_type_get_global_classfuncs 
77                                                       (camel_remote_store_get_type ()));
78         
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;
84         
85         camel_store_class->get_folder = get_folder;
86         
87         camel_remote_store_class->keepalive = imap_keepalive;
88 }
89
90 static void
91 camel_imap_store_init (gpointer object, gpointer klass)
92 {
93         CamelService *service = CAMEL_SERVICE (object);
94         CamelImapStore *imap_store = CAMEL_IMAP_STORE (object);
95         
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);
100         
101         imap_store->dir_sep = g_strdup ("/"); /*default*/
102         imap_store->current_folder = NULL;
103 }
104
105 CamelType
106 camel_imap_store_get_type (void)
107 {
108         static CamelType camel_imap_store_type = CAMEL_INVALID_TYPE;
109         
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,
116                                              NULL,
117                                              (CamelObjectInitFunc) camel_imap_store_init,
118                                              (CamelObjectFinalizeFunc) NULL);
119         }
120         
121         return camel_imap_store_type;
122 }
123
124 static CamelServiceAuthType password_authtype = {
125         "Password",
126         
127         "This option will connect to the IMAP server using a "
128         "plaintext password.",
129         
130         "",
131         TRUE
132 };
133
134 static GList *
135 query_auth_types_connected (CamelService *service, CamelException *ex)
136 {
137 #if 0   
138         GList *ret = NULL;
139         gboolean passwd = TRUE;
140         
141         if (service->url) {
142                 passwd = try_connect (service, ex);
143                 if (camel_exception_get_id (ex) != CAMEL_EXCEPTION_NONE)
144                         return NULL;
145         }
146         
147         if (passwd)
148                 ret = g_list_append (ret, &password_authtype);
149         
150         if (!ret) {
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 : 
154                                       "(unknown host)");
155         }                                     
156         
157         return ret;
158 #else
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);
162 #endif
163 }
164
165 static GList *
166 query_auth_types_generic (CamelService *service, CamelException *ex)
167 {
168         GList *prev;
169         
170         prev = CAMEL_SERVICE_CLASS (remote_store_class)->query_auth_types_generic (service, ex);
171         return g_list_prepend (prev, &password_authtype);
172 }
173
174 static gboolean
175 imap_connect (CamelService *service, CamelException *ex)
176 {
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;
181         gint status;
182         
183         if (CAMEL_SERVICE_CLASS (remote_store_class)->connect (service, ex) == FALSE)
184                 return FALSE;
185         
186         store->command = 0;
187         g_free (store->dir_sep);
188         store->dir_sep = g_strdup ("/");  /* default dir sep */
189         
190         /* Read the greeting, if any. */
191         if (camel_remote_store_recv_line (CAMEL_REMOTE_STORE (service), &buf, ex) < 0) {
192                 return FALSE;
193         }
194         g_free (buf);
195         
196         /* authenticate the user */
197         while (!authenticated) {
198                 if (errbuf) {
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;
205                 }
206                 
207                 if (!service->url->authmech && !service->url->passwd) {
208                         gchar *prompt;
209                         
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);
216                         g_free (prompt);
217                         g_free (errbuf);
218                         errbuf = NULL;
219                         
220                         if (!service->url->passwd) {
221                                 camel_exception_set (ex, CAMEL_EXCEPTION_USER_CANCEL, 
222                                                      "You didn\'t enter a password.");
223                                 return FALSE;
224                         }
225                 }
226                 
227                 status = camel_imap_command (store, NULL, ex, "LOGIN \"%s\" \"%s\"",
228                                              service->url->user,
229                                              service->url->passwd);
230                 
231                 if (status != CAMEL_IMAP_OK) {
232                         errbuf = g_strdup_printf ("Unable to authenticate to IMAP server.\n"
233                                                   "%s\n\n",
234                                                   camel_exception_get_description (ex));
235                         camel_exception_clear (ex);
236                 } else {
237                         g_message ("IMAP Service sucessfully authenticated user %s", service->url->user);
238                         authenticated = TRUE;
239                 }
240         }
241         
242         /* Now lets find out the IMAP capabilities */
243         status = camel_imap_command_extended (store, NULL, &result, ex, "CAPABILITY");
244         
245         if (status != CAMEL_IMAP_OK) {
246                 /* Non-fatal error... (ex is set) */
247         }
248         
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;
254         else
255                 store->server_level = IMAP_LEVEL_UNKNOWN;
256         
257         if ((store->server_level >= IMAP_LEVEL_IMAP4REV1) || (e_strstrcase (result, "STATUS")))
258                 store->has_status_capability = TRUE;
259         else
260                 store->has_status_capability = FALSE;
261         
262         g_free (result);
263         
264         /* We now need to find out which directory separator this daemon uses */
265         status = camel_imap_command_extended (store, NULL, &result, ex, "LIST \"\" \"\"");
266         
267         if (status != CAMEL_IMAP_OK) {
268                 /* Again, this is non-fatal */
269         } else {
270                 char *flags, *sep, *folder;
271                 
272                 if (imap_parse_list_response (result, "", &flags, &sep, &folder)) {
273                         if (*sep) {
274                                 g_free (store->dir_sep);
275                                 store->dir_sep = g_strdup (sep);
276                         }
277                 }
278                 
279                 g_free (flags);
280                 g_free (sep);
281                 g_free (folder);
282         }
283         
284         g_free (result);
285
286         camel_remote_store_refresh_folders (CAMEL_REMOTE_STORE (store), ex);
287
288         return ! camel_exception_is_set (ex);
289 }
290
291 static gboolean
292 imap_disconnect (CamelService *service, CamelException *ex)
293 {
294         CamelImapStore *store = CAMEL_IMAP_STORE (service);
295         char *result;
296         int status;
297         
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... */
302         }
303         g_free (result);
304         
305         g_free (store->dir_sep);
306         store->dir_sep = NULL;
307         
308         store->current_folder = NULL;
309         
310         return CAMEL_SERVICE_CLASS (remote_store_class)->disconnect (service, ex);
311 }
312
313 const gchar *
314 camel_imap_store_get_toplevel_dir (CamelImapStore *store)
315 {
316         CamelURL *url = CAMEL_SERVICE (store)->url;
317         
318         g_assert (url != NULL);
319         return url->path;
320 }
321
322 static gboolean
323 imap_folder_exists (CamelFolder *folder, gboolean *selectable, CamelException *ex)
324 {
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;
329         gint status;
330         
331         dir_sep = CAMEL_IMAP_STORE (folder->parent_store)->dir_sep;
332         
333         g_return_val_if_fail (dir_sep, FALSE);
334         
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);
337         else
338                 folder_path = g_strdup (folder->full_name);
339         
340         if (!g_strcasecmp (folder_path, "INBOX")) {
341                 g_free (folder_path);
342                 if (selectable)
343                         *selectable = TRUE;
344                 return TRUE;
345         }
346         
347         /* it's always gonna be FALSE unless it's true - how's that for a comment? ;-) */
348         if (selectable)
349                 *selectable = FALSE;
350         
351         status = camel_imap_command_extended (CAMEL_IMAP_STORE (store), NULL,
352                                               &result, ex, "LIST \"\" %s", folder_path);
353         
354         g_free (folder_path);
355         
356         if (status != CAMEL_IMAP_OK) {
357                 g_free (result);
358                 return FALSE;
359         }
360         
361         if (imap_parse_list_response (result, "", &flags, &sep, &dirname)) {
362                 if (selectable)
363                         *selectable = !e_strstrcase (flags, "NoSelect");
364                 
365                 g_free (flags);
366                 g_free (sep);
367                 g_free (dirname);
368                 
369                 return TRUE;
370         }
371         
372         g_free (flags);
373         g_free (sep);
374         g_free (dirname);
375         
376         return FALSE;
377 }
378
379 #if 0
380 static gboolean
381 imap_folder_exists (CamelFolder *folder, CamelException *ex)
382 {
383         CamelStore *store = CAMEL_STORE (folder->parent_store);
384         CamelURL *url = CAMEL_SERVICE (store)->url;
385         gchar *result, *folder_path, *dir_sep;
386         gint status;
387         
388         dir_sep = CAMEL_IMAP_STORE (folder->parent_store)->dir_sep;
389         
390         g_return_val_if_fail (dir_sep, FALSE);
391         
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);
394         else
395                 folder_path = g_strdup (folder->full_name);
396         
397         status = camel_imap_command_extended (CAMEL_IMAP_STORE (folder->parent_store), NULL,
398                                               &result, ex, "EXAMINE %s", folder_path);
399         
400         if (status != CAMEL_IMAP_OK) {
401                 g_free (folder_path);
402                 return FALSE;
403         }
404         g_free (folder_path);
405         g_free (result);
406         
407         return TRUE;
408 }
409 #endif
410
411 static gboolean
412 imap_create (CamelFolder *folder, CamelException *ex)
413 {
414         CamelStore *store = CAMEL_STORE (folder->parent_store);
415         CamelURL *url = CAMEL_SERVICE (store)->url;
416         gchar *result, *folder_path, *dir_sep;
417         gint status;
418         
419         g_return_val_if_fail (folder != NULL, FALSE);
420         
421         if (!(folder->full_name || folder->name)) {
422                 camel_exception_set (ex, CAMEL_EXCEPTION_FOLDER_INVALID,
423                                      "invalid folder path. Use set_name ?");
424                 return FALSE;
425         }
426         
427         if (!g_strcasecmp (folder->full_name, "INBOX"))
428                 return TRUE;
429         
430         if (imap_folder_exists (folder, NULL, ex))
431                 return TRUE;
432         
433         /* create the directory for the subfolder */
434         dir_sep = CAMEL_IMAP_STORE (folder->parent_store)->dir_sep;
435         
436         g_return_val_if_fail (dir_sep, FALSE);
437         
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);
440         else
441                 folder_path = g_strdup (folder->full_name);
442         
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);
446         
447         if (status != CAMEL_IMAP_OK)
448                 return FALSE;
449         
450         return TRUE;
451 }
452
453 #if 0
454 static gboolean
455 folder_is_selectable (CamelStore *store, const char *folder_path, CamelException *ex)
456 {
457         char *result, *flags, *sep, *folder;
458         int status;
459         
460         if (!g_strcasecmp (folder_path, "INBOX"))
461                 return TRUE;
462         
463         status = camel_imap_command_extended (CAMEL_IMAP_STORE (store), NULL,
464                                               &result, ex, "LIST \"\" %s", folder_path);
465         if (status != CAMEL_IMAP_OK)
466                 return FALSE;
467         
468         if (imap_parse_list_response (result, "", &flags, &sep, &folder)) {
469                 gboolean retval;
470                 
471                 retval = !e_strstrcase (flags, "NoSelect");
472                 g_free (flags);
473                 g_free (sep);
474                 g_free (folder);
475                 
476                 return retval;
477         }
478         g_free (flags);
479         g_free (sep);
480         g_free (folder);
481         
482         return FALSE;
483 }
484 #endif
485
486 static CamelFolder *
487 get_folder (CamelStore *store, const char *folder_name, gboolean create, CamelException *ex)
488 {
489         CamelURL *url = CAMEL_SERVICE (store)->url;
490         CamelFolder *new_folder;
491         char *folder_path, *dir_sep;
492         gboolean exists = FALSE;
493         gboolean selectable;
494         
495         g_return_val_if_fail (store != NULL, NULL);
496         g_return_val_if_fail (folder_name != NULL, NULL);
497         
498         dir_sep = CAMEL_IMAP_STORE (store)->dir_sep;
499         
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);
503         else
504                 folder_path = g_strdup (folder_name);
505         
506         new_folder = camel_imap_folder_new (store, folder_path, ex);
507         
508         if (camel_exception_is_set (ex))
509                 return NULL;
510         
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);
514                 return new_folder;
515         }
516         
517         if (imap_folder_exists (new_folder, &selectable, ex)) {
518                 /* ah huh, so the folder *does* exist... */
519                 exists = TRUE;
520                 
521                 /* now lets see if it's selectable... */
522                 if (!selectable) {
523                         camel_exception_clear (ex);
524                         new_folder->can_hold_messages = FALSE;
525                 }
526         }
527         
528         if (!exists && create && !imap_create (new_folder, ex)) {
529                 g_free (folder_path);
530                 camel_object_unref (CAMEL_OBJECT (new_folder));         
531                 return NULL;
532         }
533         
534         /* this is where we *should refresh_info, not in imap_folder_new() */
535         camel_folder_refresh_info (new_folder, ex);
536         
537         return new_folder;
538 }
539
540 static void
541 imap_keepalive (CamelRemoteStore *store)
542 {
543         CamelImapStore *imap_store = CAMEL_IMAP_STORE (store);
544         char *result;
545         int status;
546         CamelException ex;
547         
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);
552         g_free (result);
553 }
554
555 #if 0
556 static gboolean
557 stream_is_alive (CamelStream *istream)
558 {
559         CamelStreamFs *fs_stream;
560         char buf;
561         
562         g_return_val_if_fail (istream != NULL, FALSE);
563         
564         fs_stream = CAMEL_STREAM_FS (CAMEL_STREAM_BUFFER (istream)->stream);
565         g_return_val_if_fail (fs_stream->fd != -1, FALSE);
566         
567         if (read (fs_stream->fd, (void *) &buf, 0) == 0)
568                 return TRUE;
569         
570         return FALSE;
571 }
572 #endif
573
574 static int
575 camel_imap_status (char *cmdid, char *respbuf)
576 {
577         char *retcode;
578         
579         if (respbuf) {
580                 if (!strncmp (respbuf, cmdid, strlen (cmdid))) {
581                         retcode = imap_next_word (respbuf);
582                         
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;
589                 }
590         }
591         
592         return CAMEL_IMAP_FAIL;
593 }
594
595 static gint
596 check_current_folder (CamelImapStore *store, CamelFolder *folder, char *fmt, CamelException *ex)
597 {
598         CamelURL *url = CAMEL_SERVICE (store)->url;
599         char *result, *folder_path, *dir_sep;
600         int status;
601         
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;
608         
609         dir_sep = store->dir_sep;
610         
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);
613         else
614                 folder_path = g_strdup (folder->full_name);
615         
616         status = camel_imap_command_extended (store, NULL, &result, ex, "SELECT %s", folder_path);
617         g_free (folder_path);
618         
619         if (!result || status != CAMEL_IMAP_OK) {
620                 store->current_folder = NULL;
621                 return status;
622         }
623         g_free (result);
624         
625         /* remember our currently selected folder */
626         store->current_folder = folder;
627         
628         return CAMEL_IMAP_OK;
629 }
630
631 static gboolean
632 send_command (CamelImapStore *store, char **cmdid, char *fmt, va_list ap, CamelException *ex)
633 {
634         gchar *cmdbuf;
635         
636         *cmdid = g_strdup_printf ("A%.5d", store->command++);
637         
638         cmdbuf = g_strdup_vprintf (fmt, ap);
639         
640         if (camel_remote_store_send_string (CAMEL_REMOTE_STORE (store), ex, "%s %s\r\n", *cmdid, cmdbuf) < 0) {
641                 g_free (cmdbuf);
642                 g_free (*cmdid);
643                 *cmdid = NULL;
644                 return FALSE;
645         }
646         
647         g_free (cmdbuf);
648         return TRUE;
649 }
650
651
652 /**
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
659  * 
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.
668  * 
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.)
673  **/
674 gint
675 camel_imap_command (CamelImapStore *store, CamelFolder *folder, CamelException *ex, char *fmt, ...)
676 {
677         char *cmdid, *respbuf, *word;
678         gint status = CAMEL_IMAP_OK;
679         va_list ap;
680         
681         /* check for current folder */
682         status = check_current_folder (store, folder, fmt, ex);
683         if (status != CAMEL_IMAP_OK)
684                 return status;
685         
686         /* send the command */
687         va_start (ap, fmt);
688         if (!send_command (store, &cmdid, fmt, ap, ex)) {
689                 va_end (ap);
690                 g_free (cmdid);
691                 return CAMEL_IMAP_FAIL;
692         }
693         va_end (ap);
694         
695         /* read single line response */
696         if (camel_remote_store_recv_line (CAMEL_REMOTE_STORE (store), &respbuf, ex) < 0) {
697                 g_free (cmdid);
698                 return CAMEL_IMAP_FAIL;
699         }
700         
701         status = camel_imap_status (cmdid, respbuf);
702         g_free (cmdid);
703         
704         if (status == CAMEL_IMAP_OK)
705                 return status;
706         
707         if (respbuf) {
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 */
711                 
712                 camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
713                                       "IMAP command failed: %s", word);
714         } else {
715                 camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
716                                       "IMAP command failed: %s", "Unknown");
717         }
718         
719         return status;
720 }
721
722 /**
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
729  *
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.
739  * 
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.
744  * 
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.)
749  **/
750
751 gint
752 camel_imap_command_extended (CamelImapStore *store, CamelFolder *folder, char **ret, CamelException *ex, char *fmt, ...)
753 {
754         gint status = CAMEL_IMAP_OK;
755         GPtrArray *data, *expunged;
756         gchar *respbuf, *cmdid;
757         guint32 len = 0;
758         gint recent = 0;
759         va_list ap;
760         gint i;
761         
762         /* check for current folder */
763         status = check_current_folder (store, folder, fmt, ex);
764         if (status != CAMEL_IMAP_OK)
765                 return status;
766         
767         /* send the command */
768         va_start (ap, fmt);
769         if (!send_command (store, &cmdid, fmt, ap, ex)) {
770                 va_end (ap);
771                 return CAMEL_IMAP_FAIL;
772         }
773         va_end (ap);
774         
775         data = g_ptr_array_new ();
776         expunged = g_ptr_array_new ();
777         
778         /* read multi-line response */
779         while (1) {
780                 if (camel_remote_store_recv_line (CAMEL_REMOTE_STORE (store), &respbuf, ex) < 0) {
781                         /* cleanup */
782                         for (i = 0; i < data->len; i++)
783                                 g_free (data->pdata[i]);
784                         g_ptr_array_free (data, TRUE);
785                         
786                         for (i = 0; i < expunged->len; i++)
787                                 g_free (expunged->pdata[i]);
788                         g_ptr_array_free (expunged, TRUE);
789                         
790                         return CAMEL_IMAP_FAIL;
791                 }
792                 
793                 g_ptr_array_add (data, respbuf);
794                 len += strlen (respbuf) + 1;
795                 
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);
799                         break;
800                 }
801                 
802                 /* Check for a RECENT in the untagged response */
803                 if (*respbuf == '*') {
804                         if (strstr (respbuf, "RECENT")) {
805                                 char *rcnt;
806                                 
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")) {
813                                 char *id_str;
814                                 int id;
815                                 
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)) {
820                                         id = atoi (id_str);
821                                         g_ptr_array_add (expunged, g_strdup_printf ("%d", id));
822                                 }
823                         }
824                 }
825         }
826         
827         if (status == CAMEL_IMAP_OK) {
828                 gchar *p;
829                 
830                 /* populate the return buffer with the server response */
831                 *ret = g_new (char, len + 1);
832                 p = *ret;
833                 
834                 for (i = 0; i < data->len; i++) {
835                         char *datap;
836                         
837                         datap = (char *) data->pdata[i];
838                         if (*datap == '.')
839                                 datap++;
840                         len = strlen (datap);
841                         memcpy (p, datap, len);
842                         p += len;
843                         *p++ = '\n';
844                 }
845                 
846                 *p = '\0';
847         } else {
848                 /* command failed */
849                 if (respbuf) {
850                         char *word;
851                         
852                         word = imap_next_word (respbuf); /* should now point to status */
853                         
854                         word = imap_next_word (word);    /* points to fail message, if there is one */
855                         
856                         camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
857                                               "IMAP command failed: %s", word);
858                 } else {
859                         camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
860                                               "IMAP command failed: Unknown");
861                 }
862                 
863                 *ret = NULL;
864         }
865         
866         /* Update the summary */
867         if (folder && (recent > 0 || expunged->len > 0)) {
868                 CamelException dex;
869                 
870                 camel_exception_init (&dex);
871                 camel_imap_folder_changed (folder, recent, expunged, &dex);
872                 camel_exception_clear (&dex);
873         }
874         
875         for (i = 0; i < data->len; i++)
876                 g_free (data->pdata[i]);
877         g_ptr_array_free (data, TRUE);
878         
879         for (i = 0; i < expunged->len; i++)
880                 g_free (expunged->pdata[i]);
881         g_ptr_array_free (expunged, TRUE);
882         
883         return status;
884 }
885
886 /**
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
893  * 
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.
903  * 
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.)
908  **/
909
910 gint
911 camel_imap_fetch_command (CamelImapStore *store, CamelFolder *folder, char **ret, CamelException *ex, char *fmt, ...)
912 {
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.
919          */
920         gint status = CAMEL_IMAP_OK;
921         GPtrArray *data, *expunged;
922         gboolean is_notification;
923         gchar *respbuf, *cmdid;
924         guint32 len = 0;
925         gint partlen = 0;
926         gint recent = 0;
927         va_list ap;
928         gint i;
929         
930         status = check_current_folder (store, folder, fmt, ex);
931         if (status != CAMEL_IMAP_OK)
932                 return status;
933         
934         /* send the command */
935         va_start (ap, fmt);
936         if (!send_command (store, &cmdid, fmt, ap, ex)) {
937                 va_end (ap);
938                 return CAMEL_IMAP_FAIL;
939         }
940         va_end (ap);
941         
942         data = g_ptr_array_new ();
943         
944         /* get first response line */
945         if (camel_remote_store_recv_line (CAMEL_REMOTE_STORE (store), &respbuf, ex) != -1) {
946                 char *p, *q;
947                 
948                 g_ptr_array_add (data, respbuf);
949                 len += strlen (respbuf) + 1;
950                 
951                 for (p = respbuf; *p && *p != '{' && *p != '"' && *p != '\n'; p++);
952                 switch (*p) {
953                 case '"':
954                         /* a quoted string - section 4.3 */
955                         p++;
956                         for (q = p; *q && *q != '"'; q++);
957                         partlen = (guint32) (q - p);
958                         
959                         is_notification = TRUE;
960                         
961                         break;
962                 case '{':
963                         /* a literal string - section 4.3 */
964                         partlen = atoi (p + 1);
965                         
966                         /* add len to partlen because the partlen
967                            doesn't count the first response buffer */
968                         partlen += len;
969                         
970                         is_notification = FALSE;
971                         
972                         break;
973                 default:
974                         /* bad input */
975                         g_ptr_array_free (data, TRUE);
976                         return CAMEL_IMAP_FAIL;
977                 }
978         } else {
979                 g_ptr_array_free (data, TRUE);
980                 return CAMEL_IMAP_FAIL;
981         }
982         
983         expunged = g_ptr_array_new ();
984         
985         /* read multi-line response */
986         while (1) {
987                 if (camel_remote_store_recv_line (CAMEL_REMOTE_STORE (store), &respbuf, ex) < 0) {
988                         /* cleanup */
989                         for (i = 0; i < data->len; i++)
990                                 g_free (data->pdata[i]);
991                         g_ptr_array_free (data, TRUE);
992                         
993                         for (i = 0; i < expunged->len; i++)
994                                 g_free (expunged->pdata[i]);
995                         g_ptr_array_free (expunged, TRUE);
996                         
997                         return CAMEL_IMAP_FAIL;
998                 }
999                 
1000                 g_ptr_array_add (data, respbuf);
1001                 len += strlen (respbuf) + 1;
1002                 
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);
1006                         break;
1007                 }
1008                 
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"));
1015                         
1016                         recent = 0;
1017                         
1018                         for (i = 0; i < expunged->len; i++) {
1019                                 g_free (expunged->pdata[i]);
1020                                 g_ptr_array_remove_index (expunged, i);
1021                         }
1022                 }
1023                 
1024                 /* Check for a RECENT in the untagged response */
1025                 if (*respbuf == '*' && is_notification) {
1026                         if (strstr (respbuf, "RECENT")) {
1027                                 char *rcnt;
1028                                 
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")) {
1035                                 char *id_str;
1036                                 int id;
1037                                 
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)) {
1042                                         id = atoi (id_str);
1043                                         g_ptr_array_add (expunged, g_strdup_printf ("%d", id));
1044                                 }
1045                         }
1046                 }
1047                 
1048                 if (!is_notification) {
1049                         partlen--;
1050                         if (len >= partlen)
1051                                 is_notification = TRUE;
1052                 }
1053         }
1054         
1055         if (status == CAMEL_IMAP_OK) {
1056                 gchar *p;
1057                 
1058                 /* populate the return buffer with the server response */
1059                 *ret = g_new (char, len + 1);
1060                 p = *ret;
1061                 
1062                 for (i = 0; i < data->len; i++) {
1063                         char *datap;
1064                         
1065                         datap = (char *) data->pdata[i];
1066                         if (*datap == '.')
1067                                 datap++;
1068                         len = strlen (datap);
1069                         memcpy (p, datap, len);
1070                         p += len;
1071                         *p++ = '\n';
1072                 }
1073                 
1074                 *p = '\0';
1075         } else {
1076                 /* command failed */
1077                 if (respbuf) {
1078                         char *word;
1079                         
1080                         word = imap_next_word (respbuf); /* should now point to status */
1081                         
1082                         word = imap_next_word (word);    /* points to fail message, if there is one */
1083                         
1084                         camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
1085                                               "IMAP command failed: %s", word);
1086                 } else {
1087                         camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
1088                                               "IMAP command failed: Unknown");
1089                 }
1090                 
1091                 *ret = NULL;
1092         }
1093         
1094         /* Update the summary */
1095         if (folder && (recent > 0 || expunged->len > 0)) {
1096                 CamelException dex;
1097                 
1098                 camel_exception_init (&dex);
1099                 camel_imap_folder_changed (folder, recent, expunged, &dex);
1100                 camel_exception_clear (&dex);
1101         }
1102         
1103         for (i = 0; i < data->len; i++)
1104                 g_free (data->pdata[i]);
1105         g_ptr_array_free (data, TRUE);
1106         
1107         for (i = 0; i < expunged->len; i++)
1108                 g_free (expunged->pdata[i]);
1109         g_ptr_array_free (expunged, TRUE);
1110         
1111         return status;
1112 }
1113
1114 /**
1115  * camel_imap_command_preliminary: Send a preliminary command to the
1116  * IMAP server.
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
1121  * 
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.
1126  * 
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.
1131  * 
1132  * Return value: one of CAMEL_IMAP_PLUS, CAMEL_IMAP_NO, CAMEL_IMAP_BAD
1133  * or CAMEL_IMAP_FAIL
1134  * 
1135  * Note: on success (CAMEL_IMAP_PLUS), you will need to follow up with
1136  * a camel_imap_command_continuation call.
1137  **/
1138 gint
1139 camel_imap_command_preliminary (CamelImapStore *store, char **cmdid, CamelException *ex, char *fmt, ...)
1140 {
1141         char *respbuf, *word;
1142         gint status = CAMEL_IMAP_OK;
1143         va_list ap;
1144         
1145         /* send the command */
1146         va_start (ap, fmt);
1147         if (!send_command (store, cmdid, fmt, ap, ex)) {
1148                 va_end (ap);
1149                 return CAMEL_IMAP_FAIL;
1150         }
1151         va_end (ap);
1152         
1153         /* read single line response */
1154         if (camel_remote_store_recv_line (CAMEL_REMOTE_STORE (store), &respbuf, ex) < 0)
1155                 return CAMEL_IMAP_FAIL;
1156         
1157         /* Check for '+' which indicates server is ready for command continuation */
1158         if (*respbuf == '+') {
1159                 g_free (cmdid);
1160                 return CAMEL_IMAP_PLUS;
1161         }
1162         
1163         status = camel_imap_status (*cmdid, respbuf);
1164         g_free (cmdid);
1165         
1166         if (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 */
1170                 
1171                 camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
1172                                       "IMAP command failed: %s", word);
1173         } else {
1174                 camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
1175                                       "IMAP command failed: %s", "Unknown");
1176         }
1177         
1178         return status;
1179 }
1180
1181 /**
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
1188  *
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.
1194  * 
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.)
1201  **/
1202 gint
1203 camel_imap_command_continuation (CamelImapStore *store, char **ret, char *cmdid, char *cmdbuf, CamelException *ex)
1204 {
1205         gint status = CAMEL_IMAP_OK;
1206         GPtrArray *data;
1207         gchar *respbuf;
1208         guint32 len = 0;
1209         gint i;
1210         
1211         if (camel_remote_store_send_string (CAMEL_REMOTE_STORE (store), ex, "%s\r\n", cmdbuf) < 0) {
1212                 if (ret)
1213                         *ret = NULL;
1214                 return CAMEL_IMAP_FAIL;
1215         }
1216         
1217         data = g_ptr_array_new ();
1218         
1219         /* read multi-line response */
1220         while (1) {
1221                 if (camel_remote_store_recv_line (CAMEL_REMOTE_STORE (store), &respbuf, ex) < 0) {
1222                         /* cleanup */
1223                         for (i = 0; i < data->len; i++)
1224                                 g_free (data->pdata[i]);
1225                         g_ptr_array_free (data, TRUE);
1226                         
1227                         return CAMEL_IMAP_FAIL;
1228                 }
1229                 
1230                 g_ptr_array_add (data, respbuf);
1231                 len += strlen (respbuf) + 1;
1232                 
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);
1236                         break;
1237                 }
1238         }
1239         
1240         if (status == CAMEL_IMAP_OK) {
1241                 gchar *p;
1242                 
1243                 /* populate the return buffer with the server response */
1244                 *ret = g_new (char, len + 1);
1245                 p = *ret;
1246                 
1247                 for (i = 0; i < data->len; i++) {
1248                         char *datap;
1249                         
1250                         datap = (char *) data->pdata[i];
1251                         if (*datap == '.')
1252                                 datap++;
1253                         len = strlen (datap);
1254                         memcpy (p, datap, len);
1255                         p += len;
1256                         *p++ = '\n';
1257                 }
1258                 
1259                 *p = '\0';
1260         } else {
1261                 /* command failed */
1262                 if (respbuf) {
1263                         char *word;
1264                         
1265                         word = imap_next_word (respbuf); /* should now point to status */
1266                         
1267                         word = imap_next_word (word);    /* points to fail message, if there is one */
1268                         
1269                         camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
1270                                               "IMAP command failed: %s", word);
1271                 } else {
1272                         camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
1273                                               "IMAP command failed: Unknown");
1274                 }
1275                 
1276                 *ret = NULL;
1277         }
1278         
1279         /* cleanup */
1280         for (i = 0; i < data->len; i++)
1281                 g_free (data->pdata[i]);
1282         g_ptr_array_free (data, TRUE);
1283         
1284         return status;
1285 }
1286
1287 /**
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.
1294  * 
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.
1300  * 
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.)
1307  **/
1308 gint
1309 camel_imap_command_continuation_with_stream (CamelImapStore *store, char **ret, char *cmdid,
1310                                              CamelStream *cstream, CamelException *ex)
1311 {
1312         gint status = CAMEL_IMAP_OK;
1313         GPtrArray *data;
1314         gchar *respbuf;
1315         guint32 len = 0;
1316         gint i;
1317         
1318         /* send stream */
1319         if (camel_remote_store_send_stream (CAMEL_REMOTE_STORE (store), cstream, ex) < 0) {
1320                 if (ret)
1321                         *ret = NULL;
1322                 return CAMEL_IMAP_FAIL;
1323         }
1324         
1325         data = g_ptr_array_new ();
1326         
1327         /* read the servers multi-line response */
1328         while (1) {
1329                 if (camel_remote_store_recv_line (CAMEL_REMOTE_STORE (store), &respbuf, ex) < 0) {
1330                         /* cleanup */
1331                         for (i = 0; i < data->len; i++)
1332                                 g_free (data->pdata[i]);
1333                         g_ptr_array_free (data, TRUE);
1334                         
1335                         return CAMEL_IMAP_FAIL;
1336                 }
1337                 
1338                 g_ptr_array_add (data, respbuf);
1339                 len += strlen (respbuf) + 1;
1340                 
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);
1344                         break;
1345                 }
1346         }
1347         
1348         if (status == CAMEL_IMAP_OK) {
1349                 gchar *p;
1350                 
1351                 /* populate the return buffer with the server response */
1352                 *ret = g_new (char, len + 1);
1353                 p = *ret;
1354                 
1355                 for (i = 0; i < data->len; i++) {
1356                         char *datap;
1357                         
1358                         datap = (char *) data->pdata[i];
1359                         if (*datap == '.')
1360                                 datap++;
1361                         len = strlen (datap);
1362                         memcpy (p, datap, len);
1363                         p += len;
1364                         *p++ = '\n';
1365                 }
1366                 
1367                 *p = '\0';
1368         } else {
1369                 /* command failed */
1370                 if (respbuf) {
1371                         char *word;
1372                         
1373                         word = imap_next_word (respbuf); /* should now point to status */
1374                         
1375                         word = imap_next_word (word);    /* points to fail message, if there is one */
1376                         
1377                         camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
1378                                               "IMAP command failed: %s", word);
1379                 } else {
1380                         camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
1381                                               "IMAP command failed: Unknown error");
1382                 }
1383                 
1384                 *ret = NULL;
1385         }
1386         
1387         /* cleanup */
1388         for (i = 0; i < data->len; i++)
1389                 g_free (data->pdata[i]);
1390         g_ptr_array_free (data, TRUE);
1391         
1392         return status;
1393 }