Fix FSF address (Tobias Mueller, #470445)
[platform/upstream/evolution-data-server.git] / camel / providers / imap / camel-imap-command.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /* camel-imap-command.c: IMAP command sending/parsing routines */
3
4 /*
5  *  Authors:
6  *    Dan Winship <danw@ximian.com>
7  *    Jeffrey Stedfast <fejj@ximian.com>
8  *
9  *  Copyright 2000, 2001 Ximian, Inc.
10  *
11  * This program is free software; you can redistribute it and/or
12  * modify it under the terms of version 2 of the GNU Lesser General Public
13  * License as published by the Free Software Foundation.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18  * General Public License for more details.
19  *
20  * You should have received a copy of the GNU Lesser General Public
21  * License along with this program; if not, write to the
22  * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
23  * Boston, MA 02110-1301, USA.
24  *
25  */
26
27 #ifdef HAVE_CONFIG_H
28 #include <config.h>
29 #endif
30
31 #include <errno.h>
32 #include <stdarg.h>
33 #include <stdio.h>
34 #include <string.h>
35
36 #include <glib/gi18n-lib.h>
37
38 #include "camel-debug.h"
39 #include "camel-exception.h"
40 #include "camel-private.h"
41 #include "camel-session.h"
42 #include "camel-utf8.h"
43
44 #include "camel-imap-command.h"
45 #include "camel-imap-folder.h"
46 #include "camel-imap-store-summary.h"
47 #include "camel-imap-store.h"
48 #include "camel-imap-utils.h"
49
50 extern int camel_verbose_debug;
51
52 static gboolean imap_command_start (CamelImapStore *store, CamelFolder *folder,
53                                     const char *cmd, CamelException *ex);
54 static CamelImapResponse *imap_read_response (CamelImapStore *store,
55                                               CamelException *ex);
56 static char *imap_read_untagged (CamelImapStore *store, char *line,
57                                  CamelException *ex);
58 static char *imap_command_strdup_vprintf (CamelImapStore *store,
59                                           const char *fmt, va_list ap);
60 static char *imap_command_strdup_printf (CamelImapStore *store,
61                                          const char *fmt, ...);
62
63 /**
64  * camel_imap_command:
65  * @store: the IMAP store
66  * @folder: The folder to perform the operation in (or %NULL if not
67  * relevant).
68  * @ex: a CamelException
69  * @fmt: a sort of printf-style format string, followed by arguments
70  *
71  * This function calls camel_imap_command_start() to send the
72  * command, then reads the complete response to it using
73  * camel_imap_command_response() and returns a CamelImapResponse
74  * structure.
75  *
76  * As a special case, if @fmt is %NULL, it will just select @folder
77  * and return the response from doing so.
78  *
79  * See camel_imap_command_start() for details on @fmt.
80  *
81  * On success, the store's connect_lock will be locked. It will be freed
82  * when you call camel_imap_response_free. (The lock is recursive, so
83  * callers can grab and release it themselves if they need to run
84  * multiple commands atomically.)
85  *
86  * Return value: %NULL if an error occurred (in which case @ex will
87  * be set). Otherwise, a CamelImapResponse describing the server's
88  * response, which the caller must free with camel_imap_response_free().
89  **/
90 CamelImapResponse *
91 camel_imap_command (CamelImapStore *store, CamelFolder *folder,
92                     CamelException *ex, const char *fmt, ...)
93 {
94         va_list ap;
95         char *cmd;
96         
97         CAMEL_SERVICE_REC_LOCK (store, connect_lock);
98         
99         if (fmt) {
100                 va_start (ap, fmt);
101                 cmd = imap_command_strdup_vprintf (store, fmt, ap);
102                 va_end (ap);
103         } else {
104                 camel_object_ref(folder);
105                 if (store->current_folder)
106                         camel_object_unref(store->current_folder);
107                 store->current_folder = folder;
108                 cmd = imap_command_strdup_printf (store, "SELECT %F", folder->full_name);
109         }
110         
111         if (!imap_command_start (store, folder, cmd, ex)) {
112                 g_free (cmd);
113                 CAMEL_SERVICE_REC_UNLOCK (store, connect_lock);
114                 return NULL;
115         }
116         g_free (cmd);
117         
118         return imap_read_response (store, ex);
119 }
120
121 /**
122  * camel_imap_command_start:
123  * @store: the IMAP store
124  * @folder: The folder to perform the operation in (or %NULL if not
125  * relevant).
126  * @ex: a CamelException
127  * @fmt: a sort of printf-style format string, followed by arguments
128  *
129  * This function makes sure that @folder (if non-%NULL) is the
130  * currently-selected folder on @store and then sends the IMAP command
131  * specified by @fmt and the following arguments.
132  *
133  * @fmt can include the following %-escapes ONLY:
134  *      %s, %d, %%: as with printf
135  *      %S: an IMAP "string" (quoted string or literal)
136  *      %F: an IMAP folder name
137  *      %G: an IMAP folder name, with namespace already prepended
138  *
139  * %S strings will be passed as literals if the server supports LITERAL+
140  * and quoted strings otherwise. (%S does not support strings that
141  * contain newlines.)
142  *
143  * %F will have the imap store's namespace prepended; %F and %G will then
144  * be converted to UTF-7 and processed like %S.
145  *
146  * On success, the store's connect_lock will be locked. It will be
147  * freed when %CAMEL_IMAP_RESPONSE_TAGGED or %CAMEL_IMAP_RESPONSE_ERROR
148  * is returned from camel_imap_command_response(). (The lock is
149  * recursive, so callers can grab and release it themselves if they
150  * need to run multiple commands atomically.)
151  *
152  * Return value: %TRUE if the command was sent successfully, %FALSE if
153  * an error occurred (in which case @ex will be set).
154  **/
155 gboolean
156 camel_imap_command_start (CamelImapStore *store, CamelFolder *folder,
157                           CamelException *ex, const char *fmt, ...)
158 {
159         va_list ap;
160         char *cmd;
161         gboolean ok;
162         
163         va_start (ap, fmt);
164         cmd = imap_command_strdup_vprintf (store, fmt, ap);
165         va_end (ap);
166         
167         CAMEL_SERVICE_REC_LOCK (store, connect_lock);
168         ok = imap_command_start (store, folder, cmd, ex);
169         g_free (cmd);
170         
171         if (!ok)
172                 CAMEL_SERVICE_REC_UNLOCK (store, connect_lock);
173         return ok;
174 }
175
176 static gboolean
177 imap_command_start (CamelImapStore *store, CamelFolder *folder,
178                     const char *cmd, CamelException *ex)
179 {
180         ssize_t nwritten;
181         
182         g_return_val_if_fail(store->ostream!=NULL, FALSE);
183         g_return_val_if_fail(store->istream!=NULL, FALSE);
184         
185         /* Check for current folder */
186         if (folder && folder != store->current_folder) {
187                 CamelImapResponse *response;
188                 CamelException internal_ex;
189                 
190                 response = camel_imap_command (store, folder, ex, NULL);
191                 if (!response)
192                         return FALSE;
193                 camel_exception_init (&internal_ex);
194                 camel_imap_folder_selected (folder, response, &internal_ex);
195                 camel_imap_response_free (store, response);
196                 if (camel_exception_is_set (&internal_ex)) {
197                         camel_exception_xfer (ex, &internal_ex);
198                         return FALSE;
199                 }
200         }
201         
202         /* Send the command */
203         if (camel_verbose_debug) {
204                 const char *mask;
205                 
206                 if (!strncmp ("LOGIN \"", cmd, 7))
207                         mask = "LOGIN \"xxx\" xxx";
208                 else if (!strncmp ("LOGIN {", cmd, 7))
209                         mask = "LOGIN {N+}\r\nxxx {N+}\r\nxxx";
210                 else if (!strncmp ("LOGIN ", cmd, 6))
211                         mask = "LOGIN xxx xxx";
212                 else
213                         mask = cmd;
214                 
215                 fprintf (stderr, "sending : %c%.5u %s\r\n", store->tag_prefix, store->command, mask);
216         }
217         
218         nwritten = camel_stream_printf (store->ostream, "%c%.5u %s\r\n",
219                                         store->tag_prefix, store->command++, cmd);
220         
221         if (nwritten == -1) {
222                 if (errno == EINTR)
223                         camel_exception_set (ex, CAMEL_EXCEPTION_USER_CANCEL,
224                                              _("Operation cancelled"));
225                 else
226                         camel_exception_set (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
227                                              g_strerror (errno));
228                 
229                 camel_service_disconnect (CAMEL_SERVICE (store), FALSE, NULL);
230                 return FALSE;
231         }
232         
233         return TRUE;
234 }
235
236 /**
237  * camel_imap_command_continuation:
238  * @store: the IMAP store
239  * @cmd: buffer containing the response/request data
240  * @cmdlen: command length
241  * @ex: a CamelException
242  *
243  * This method is for sending continuing responses to the IMAP server
244  * after camel_imap_command() or camel_imap_command_response() returns
245  * a continuation response.
246  * 
247  * This function assumes you have an exclusive lock on the imap stream.
248  *
249  * Return value: as for camel_imap_command(). On failure, the store's
250  * connect_lock will be released.
251  **/
252 CamelImapResponse *
253 camel_imap_command_continuation (CamelImapStore *store, const char *cmd,
254                                  size_t cmdlen, CamelException *ex)
255 {
256         if (!camel_imap_store_connected (store, ex))
257                 return NULL;
258
259         g_return_val_if_fail(store->ostream!=NULL, NULL);
260         g_return_val_if_fail(store->istream!=NULL, NULL);
261         
262         if (camel_stream_write (store->ostream, cmd, cmdlen) == -1 ||
263             camel_stream_write (store->ostream, "\r\n", 2) == -1) {
264                 if (errno == EINTR)
265                         camel_exception_set (ex, CAMEL_EXCEPTION_USER_CANCEL,
266                                              _("Operation cancelled"));
267                 else
268                         camel_exception_set (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
269                                              g_strerror (errno));
270                 camel_service_disconnect (CAMEL_SERVICE (store), FALSE, NULL);
271                 CAMEL_SERVICE_REC_UNLOCK (store, connect_lock);
272                 return NULL;
273         }
274         
275         return imap_read_response (store, ex);
276 }
277
278 /**
279  * camel_imap_command_response:
280  * @store: the IMAP store
281  * @response: a pointer to pass back the response data in
282  * @ex: a CamelException
283  *
284  * This reads a single tagged, untagged, or continuation response from
285  * @store into *@response. The caller must free the string when it is
286  * done with it.
287  *
288  * Return value: One of %CAMEL_IMAP_RESPONSE_CONTINUATION,
289  * %CAMEL_IMAP_RESPONSE_UNTAGGED, %CAMEL_IMAP_RESPONSE_TAGGED, or
290  * %CAMEL_IMAP_RESPONSE_ERROR. If either of the last two, @store's
291  * command lock will be unlocked.
292  **/
293 CamelImapResponseType
294 camel_imap_command_response (CamelImapStore *store, char **response,
295                              CamelException *ex)
296 {
297         CamelImapResponseType type;
298         char *respbuf;
299         
300         if (camel_imap_store_readline (store, &respbuf, ex) < 0) {
301                 CAMEL_SERVICE_REC_UNLOCK (store, connect_lock);
302                 return CAMEL_IMAP_RESPONSE_ERROR;
303         }
304         
305         switch (*respbuf) {
306         case '*':
307                 if (!g_ascii_strncasecmp (respbuf, "* BYE", 5)) {
308                         /* Connection was lost, no more data to fetch */
309                         camel_service_disconnect (CAMEL_SERVICE (store), FALSE, NULL);
310                         camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
311                                               _("Server unexpectedly disconnected: %s"),
312                                               _("Unknown error")); /* g_strerror (104));  FIXME after 1.0 is released */
313                         store->connected = FALSE;
314                         g_free (respbuf);
315                         respbuf = NULL;
316                         type = CAMEL_IMAP_RESPONSE_ERROR;
317                         break;
318                 }
319                 
320                 /* Read the rest of the response. */
321                 type = CAMEL_IMAP_RESPONSE_UNTAGGED;
322                 respbuf = imap_read_untagged (store, respbuf, ex);
323                 if (!respbuf)
324                         type = CAMEL_IMAP_RESPONSE_ERROR;
325                 else if (!g_ascii_strncasecmp (respbuf, "* OK [ALERT]", 12)
326                          || !g_ascii_strncasecmp (respbuf, "* NO [ALERT]", 12)
327                          || !g_ascii_strncasecmp (respbuf, "* BAD [ALERT]", 13)) {
328                         char *msg;
329
330                         /* for imap ALERT codes, account user@host */
331                         /* we might get a ']' from a BAD response since we +12, but who cares? */
332                         msg = g_strdup_printf(_("Alert from IMAP server %s@%s:\n%s"),
333                                               ((CamelService *)store)->url->user, ((CamelService *)store)->url->host, respbuf+12);
334                         camel_session_alert_user(((CamelService *)store)->session, CAMEL_SESSION_ALERT_WARNING, msg, FALSE);
335                         g_free(msg);
336                 }
337                 
338                 break;
339         case '+':
340                 type = CAMEL_IMAP_RESPONSE_CONTINUATION;
341                 break;
342         default:
343                 type = CAMEL_IMAP_RESPONSE_TAGGED;
344                 break;
345         }
346         *response = respbuf;
347         
348         if (type == CAMEL_IMAP_RESPONSE_ERROR ||
349             type == CAMEL_IMAP_RESPONSE_TAGGED)
350                 CAMEL_SERVICE_REC_UNLOCK (store, connect_lock);
351         
352         return type;
353 }
354
355 CamelImapResponse *
356 imap_read_response (CamelImapStore *store, CamelException *ex)
357 {
358         CamelImapResponse *response;
359         CamelImapResponseType type;
360         char *respbuf, *p;
361         
362         /* Get another lock so that when we reach the tagged
363          * response and camel_imap_command_response unlocks,
364          * we're still locked. This lock is owned by response
365          * and gets unlocked when response is freed.
366          */
367         CAMEL_SERVICE_REC_LOCK (store, connect_lock);
368         
369         response = g_new0 (CamelImapResponse, 1);
370         if (store->current_folder && camel_disco_store_status (CAMEL_DISCO_STORE (store)) != CAMEL_DISCO_STORE_RESYNCING) {
371                 response->folder = store->current_folder;
372                 camel_object_ref (CAMEL_OBJECT (response->folder));
373         }
374         
375         response->untagged = g_ptr_array_new ();
376         while ((type = camel_imap_command_response (store, &respbuf, ex))
377                == CAMEL_IMAP_RESPONSE_UNTAGGED)
378                 g_ptr_array_add (response->untagged, respbuf);
379         
380         if (type == CAMEL_IMAP_RESPONSE_ERROR) {
381                 camel_imap_response_free_without_processing (store, response);
382                 return NULL;
383         }
384         
385         response->status = respbuf;
386         
387         /* Check for OK or continuation response. */
388         if (*respbuf == '+')
389                 return response;
390         p = strchr (respbuf, ' ');
391         if (p && !g_ascii_strncasecmp (p, " OK", 3))
392                 return response;
393         
394         /* We should never get BAD, or anything else but +, OK, or NO
395          * for that matter.  Well, we could get BAD, treat as NO.
396          */
397         if (!p || (g_ascii_strncasecmp(p, " NO", 3) != 0 && g_ascii_strncasecmp(p, " BAD", 4)) ) {
398                 g_warning ("Unexpected response from IMAP server: %s",
399                            respbuf);
400                 camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
401                                       _("Unexpected response from IMAP "
402                                         "server: %s"), respbuf);
403                 camel_imap_response_free_without_processing (store, response);
404                 return NULL;
405         }
406         
407         p += 3;
408         if (!*p++)
409                 p = NULL;
410         camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_INVALID,
411                               _("IMAP command failed: %s"),
412                               p ? p : _("Unknown error"));
413         camel_imap_response_free_without_processing (store, response);
414         return NULL;
415 }
416
417 /* Given a line that is the start of an untagged response, read and
418  * return the complete response, which may include an arbitrary number
419  * of literals.
420  */
421 static char *
422 imap_read_untagged (CamelImapStore *store, char *line, CamelException *ex)
423 {
424         int fulllen, ldigits, nread, n, i, sexp = 0;
425         unsigned int length;
426         GPtrArray *data;
427         GString *str;
428         char *end, *p, *s, *d;
429         
430         p = strrchr (line, '{');
431         if (!p)
432                 return line;
433         
434         data = g_ptr_array_new ();
435         fulllen = 0;
436         
437         while (1) {
438                 str = g_string_new (line);
439                 g_free (line);
440                 fulllen += str->len;
441                 g_ptr_array_add (data, str);
442                 
443                 if (!(p = strrchr (str->str, '{')) || p[1] == '-')
444                         break;
445                 
446                 /* HACK ALERT: We scan the non-literal part of the string, looking for possible s expression braces.
447                    This assumes we're getting s-expressions, which we should be.
448                    This is so if we get a blank line after a literal, in an s-expression, we can keep going, since
449                    we do no other parsing at this level.
450                    TODO: handle quoted strings? */
451                 for (s=str->str; s<p; s++) {
452                         if (*s == '(')
453                                 sexp++;
454                         else if (*s == ')')
455                                 sexp--;
456                 }
457                 
458                 length = strtoul (p + 1, &end, 10);
459                 if (*end != '}' || *(end + 1) || end == p + 1 || length >= UINT_MAX - 2)
460                         break;
461                 ldigits = end - (p + 1);
462                 
463                 /* Read the literal */
464                 str = g_string_sized_new (length + 2);
465                 str->str[0] = '\n';
466                 nread = 0;
467                 
468                 do {
469                         if ((n = camel_stream_read (store->istream, str->str + nread + 1, length - nread)) == -1) {
470                                 if (errno == EINTR)
471                                         camel_exception_set (ex, CAMEL_EXCEPTION_USER_CANCEL,
472                                                              _("Operation cancelled"));
473                                 else
474                                         camel_exception_set (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
475                                                              g_strerror (errno));
476                                 camel_service_disconnect (CAMEL_SERVICE (store), FALSE, NULL);
477                                 g_string_free (str, TRUE);
478                                 goto lose;
479                         }
480                         
481                         nread += n;
482                 } while (n > 0 && nread < length);
483                 
484                 if (nread < length) {
485                         camel_exception_set (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
486                                              _("Server response ended too soon."));
487                         camel_service_disconnect (CAMEL_SERVICE (store), FALSE, NULL);
488                         g_string_free (str, TRUE);
489                         goto lose;
490                 }
491                 str->str[length + 1] = '\0';
492
493                 if (camel_debug("imap")) {
494                         printf("Literal: -->");
495                         fwrite(str->str+1, 1, length, stdout);
496                         printf("<--\n");
497                 }
498                 
499                 /* Fix up the literal, turning CRLFs into LF. Also, if
500                  * we find any embedded NULs, strip them. This is
501                  * dubious, but:
502                  *   - The IMAP grammar says you can't have NULs here
503                  *     anyway, so this will not affect our behavior
504                  *     against any completely correct server.
505                  *   - WU-imapd 12.264 (at least) will cheerily pass
506                  *     NULs along if they are embedded in the message
507                  */
508                 
509                 s = d = str->str + 1;
510                 end = str->str + 1 + length;
511                 while (s < end) {
512                         while (s < end && *s == '\0') {
513                                 s++;
514                                 length--;
515                         }
516                         if (*s == '\r' && *(s + 1) == '\n') {
517                                 s++;
518                                 length--;
519                         }
520                         *d++ = *s++;
521                 }
522                 *d = '\0';
523                 str->len = length + 1;
524                 
525                 /* p points to the "{" in the line that starts the
526                  * literal. The length of the CR-less response must be
527                  * less than or equal to the length of the response
528                  * with CRs, therefore overwriting the old value with
529                  * the new value cannot cause an overrun. However, we
530                  * don't want it to be shorter either, because then the
531                  * GString's length would be off...
532                  */
533                 sprintf (p, "{%0*u}", ldigits, length);
534                 
535                 fulllen += str->len;
536                 g_ptr_array_add (data, str);
537
538                 /* Read the next line. */
539                 do {
540                         if (camel_imap_store_readline (store, &line, ex) < 0)
541                                 goto lose;
542
543                         /* MAJOR HACK ALERT, gropuwise sometimes sends an extra blank line after literals, check that here
544                            But only do it if we're inside an sexpression */
545                         if (line[0] == 0 && sexp > 0)
546                                 g_warning("Server sent empty line after a literal, assuming in error");
547                 } while (line[0] == 0 && sexp > 0);
548         }
549         
550         /* Now reassemble the data. */
551         p = line = g_malloc (fulllen + 1);
552         for (i = 0; i < data->len; i++) {
553                 str = data->pdata[i];
554                 memcpy (p, str->str, str->len);
555                 p += str->len;
556                 g_string_free (str, TRUE);
557         }
558         *p = '\0';
559         g_ptr_array_free (data, TRUE);
560         return line;
561         
562  lose:
563         for (i = 0; i < data->len; i++)
564                 g_string_free (data->pdata[i], TRUE);
565         g_ptr_array_free (data, TRUE);
566         return NULL;
567 }
568
569
570 /**
571  * camel_imap_response_free:
572  * @store: the CamelImapStore the response is from
573  * @response: a CamelImapResponse
574  *
575  * Frees all of the data in @response and processes any untagged
576  * EXPUNGE and EXISTS responses in it. Releases @store's connect_lock.
577  **/
578 void
579 camel_imap_response_free (CamelImapStore *store, CamelImapResponse *response)
580 {
581         int i, number, exists = 0;
582         GArray *expunged = NULL;
583         char *resp, *p;
584         
585         if (!response)
586                 return;
587         
588         for (i = 0; i < response->untagged->len; i++) {
589                 resp = response->untagged->pdata[i];
590                 
591                 if (response->folder) {
592                         /* Check if it's something we need to handle. */
593                         number = strtoul (resp + 2, &p, 10);
594                         if (!g_ascii_strcasecmp (p, " EXISTS")) {
595                                 exists = number;
596                         } else if (!g_ascii_strcasecmp (p, " EXPUNGE")
597                                    || !g_ascii_strcasecmp(p, " XGWMOVE")) {
598                                 /* XGWMOVE response is the same as an EXPUNGE response */
599                                 if (!expunged) {
600                                         expunged = g_array_new (FALSE, FALSE,
601                                                                 sizeof (int));
602                                 }
603                                 g_array_append_val (expunged, number);
604                         }
605                 }
606                 g_free (resp);
607         }
608         
609         g_ptr_array_free (response->untagged, TRUE);
610         g_free (response->status);
611         
612         if (response->folder) {
613                 if (exists > 0 || expunged) {
614                         /* Update the summary */
615                         camel_imap_folder_changed (response->folder,
616                                                    exists, expunged, NULL);
617                         if (expunged)
618                                 g_array_free (expunged, TRUE);
619                 }
620                 
621                 camel_object_unref (CAMEL_OBJECT (response->folder));
622         }
623         
624         g_free (response);
625         CAMEL_SERVICE_REC_UNLOCK (store, connect_lock);
626 }
627
628 /**
629  * camel_imap_response_free_without_processing:
630  * @store: the CamelImapStore the response is from.
631  * @response: a CamelImapResponse:
632  *
633  * Frees all of the data in @response without processing any untagged
634  * responses. Releases @store's command lock.
635  **/
636 void
637 camel_imap_response_free_without_processing (CamelImapStore *store,
638                                              CamelImapResponse *response)
639 {
640         if (!response)
641                 return;
642         
643         if (response->folder) {
644                 camel_object_unref (CAMEL_OBJECT (response->folder));
645                 response->folder = NULL;
646         }
647         camel_imap_response_free (store, response);
648 }
649
650 /**
651  * camel_imap_response_extract:
652  * @store: the store the response came from
653  * @response: the response data returned from camel_imap_command
654  * @type: the response type to extract
655  * @ex: a CamelException
656  *
657  * This checks that @response contains a single untagged response of
658  * type @type and returns just that response data. If @response
659  * doesn't contain the right information, the function will set @ex
660  * and return %NULL. Either way, @response will be freed and the
661  * store's connect_lock released.
662  *
663  * Return value: the desired response string, which the caller must free.
664  **/
665 char *
666 camel_imap_response_extract (CamelImapStore *store,
667                              CamelImapResponse *response,
668                              const char *type,
669                              CamelException *ex)
670 {
671         int len = strlen (type), i;
672         char *resp;
673         
674         len = strlen (type);
675         
676         for (i = 0; i < response->untagged->len; i++) {
677                 resp = response->untagged->pdata[i];
678                 /* Skip "* ", and initial sequence number, if present */
679                 strtoul (resp + 2, &resp, 10);
680                 if (*resp == ' ')
681                         resp = (char *) imap_next_word (resp);
682                 
683                 if (!g_ascii_strncasecmp (resp, type, len))
684                         break;
685         }
686         
687         if (i < response->untagged->len) {
688                 resp = response->untagged->pdata[i];
689                 g_ptr_array_remove_index (response->untagged, i);
690         } else {
691                 resp = NULL;
692                 camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
693                                       _("IMAP server response did not contain "
694                                         "%s information"), type);
695         }
696         
697         camel_imap_response_free (store, response);
698         return resp;
699 }
700
701 /**
702  * camel_imap_response_extract_continuation:
703  * @store: the store the response came from
704  * @response: the response data returned from camel_imap_command
705  * @ex: a CamelException
706  *
707  * This checks that @response contains a continuation response, and
708  * returns just that data. If @response doesn't contain a continuation
709  * response, the function will set @ex, release @store's connect_lock,
710  * and return %NULL. Either way, @response will be freed.
711  *
712  * Return value: the desired response string, which the caller must free.
713  **/
714 char *
715 camel_imap_response_extract_continuation (CamelImapStore *store,
716                                           CamelImapResponse *response,
717                                           CamelException *ex)
718 {
719         char *status;
720         
721         if (response->status && *response->status == '+') {
722                 status = response->status;
723                 response->status = NULL;
724                 camel_imap_response_free (store, response);
725                 return status;
726         }
727         
728         camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
729                               _("Unexpected OK response from IMAP server: %s"),
730                               response->status);
731         camel_imap_response_free (store, response);
732         return NULL;
733 }
734
735 static char *
736 imap_command_strdup_vprintf (CamelImapStore *store, const char *fmt,
737                              va_list ap)
738 {
739         GPtrArray *args;
740         const char *p, *start;
741         char *out, *outptr, *string;
742         int num, len, i, arglen;
743
744         args = g_ptr_array_new ();
745         
746         /* Determine the length of the data */
747         len = strlen (fmt);
748         p = start = fmt;
749         while (*p) {
750                 p = strchr (start, '%');
751                 if (!p)
752                         break;
753                 
754                 switch (*++p) {
755                 case 'd':
756                         num = va_arg (ap, int);
757                         g_ptr_array_add (args, GINT_TO_POINTER (num));
758                         start = p + 1;
759                         len += 10;
760                         break;
761                 case 's':
762                         string = va_arg (ap, char *);
763                         g_ptr_array_add (args, string);
764                         start = p + 1;
765                         len += strlen (string);
766                         break;
767                 case 'S':
768                 case 'F':
769                 case 'G':
770                         string = va_arg (ap, char *);
771                         /* NB: string is freed during output for %F and %G */
772                         if (*p == 'F') {
773                                 char *s = camel_imap_store_summary_full_from_path(store->summary, string);
774                                 if (s) {
775                                         string = camel_utf8_utf7(s);
776                                         g_free(s);
777                                 } else {
778                                         string = camel_utf8_utf7(string);
779                                 }
780                         } else if (*p == 'G') {
781                                 string = camel_utf8_utf7(string);
782                         }
783                                 
784                         arglen = strlen (string);
785                         g_ptr_array_add (args, string);
786                         if (imap_is_atom (string)) {
787                                 len += arglen;
788                         } else {
789                                 if (store->capabilities & IMAP_CAPABILITY_LITERALPLUS)
790                                         len += arglen + 15;
791                                 else
792                                         len += arglen * 2;
793                         }
794                         start = p + 1;
795                         break;
796                 case '%':
797                         start = p;
798                         break;
799                 default:
800                         g_warning ("camel-imap-command is not printf. I don't "
801                                    "know what '%%%c' means.", *p);
802                         start = *p ? p + 1 : p;
803                         break;
804                 }
805         }
806         
807         /* Now write out the string */
808         outptr = out = g_malloc (len + 1);
809         p = start = fmt;
810         i = 0;
811         while (*p) {
812                 p = strchr (start, '%');
813                 if (!p) {
814                         strcpy (outptr, start);
815                         break;
816                 } else {
817                         strncpy (outptr, start, p - start);
818                         outptr += p - start;
819                 }
820                 
821                 switch (*++p) {
822                 case 'd':
823                         num = GPOINTER_TO_INT (args->pdata[i++]);
824                         outptr += sprintf (outptr, "%d", num);
825                         break;
826                         
827                 case 's':
828                         string = args->pdata[i++];
829                         outptr += sprintf (outptr, "%s", string);
830                         break;
831                 case 'S':
832                 case 'F':
833                 case 'G':
834                         string = args->pdata[i++];
835                         if (imap_is_atom (string)) {
836                                 outptr += sprintf (outptr, "%s", string);
837                         } else {
838                                 len = strlen (string);
839                                 if (len && store->capabilities & IMAP_CAPABILITY_LITERALPLUS) {
840                                         outptr += sprintf (outptr, "{%d+}\r\n%s", len, string);
841                                 } else {
842                                         char *quoted = imap_quote_string (string);
843                                         
844                                         outptr += sprintf (outptr, "%s", quoted);
845                                         g_free (quoted);
846                                 }
847                         }
848                         
849                         if (*p == 'F' || *p == 'G')
850                                 g_free (string);
851                         break;
852                 default:
853                         *outptr++ = '%';
854                         *outptr++ = *p;
855                 }
856                 
857                 start = *p ? p + 1 : p;
858         }
859         
860         g_ptr_array_free (args, TRUE);
861
862         return out;
863 }
864
865 static char *
866 imap_command_strdup_printf (CamelImapStore *store, const char *fmt, ...)
867 {
868         va_list ap;
869         char *result;
870         
871         va_start (ap, fmt);
872         result = imap_command_strdup_vprintf (store, fmt, ap);
873         va_end (ap);
874         
875         return result;
876 }