1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /* camel-imap-command.c: IMAP command sending/parsing routines */
6 * Dan Winship <danw@ximian.com>
7 * Jeffrey Stedfast <fejj@ximian.com>
9 * Copyright 2000, 2001 Ximian, Inc.
11 * This program is free software; you can redistribute it and/or
12 * modify it under the terms of version 2 of the GNU Lesser General Public
13 * License as published by the Free Software Foundation.
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 * General Public License for more details.
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.
36 #include <glib/gi18n-lib.h>
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"
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"
50 extern int camel_verbose_debug;
52 static gboolean imap_command_start (CamelImapStore *store, CamelFolder *folder,
53 const char *cmd, CamelException *ex);
54 static CamelImapResponse *imap_read_response (CamelImapStore *store,
56 static char *imap_read_untagged (CamelImapStore *store, char *line,
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, ...);
65 * @store: the IMAP store
66 * @folder: The folder to perform the operation in (or %NULL if not
68 * @ex: a CamelException
69 * @fmt: a sort of printf-style format string, followed by arguments
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
76 * As a special case, if @fmt is %NULL, it will just select @folder
77 * and return the response from doing so.
79 * See camel_imap_command_start() for details on @fmt.
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.)
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().
91 camel_imap_command (CamelImapStore *store, CamelFolder *folder,
92 CamelException *ex, const char *fmt, ...)
97 CAMEL_SERVICE_REC_LOCK (store, connect_lock);
101 cmd = imap_command_strdup_vprintf (store, fmt, ap);
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);
111 if (!imap_command_start (store, folder, cmd, ex)) {
113 CAMEL_SERVICE_REC_UNLOCK (store, connect_lock);
118 return imap_read_response (store, ex);
122 * camel_imap_command_start:
123 * @store: the IMAP store
124 * @folder: The folder to perform the operation in (or %NULL if not
126 * @ex: a CamelException
127 * @fmt: a sort of printf-style format string, followed by arguments
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.
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
139 * %S strings will be passed as literals if the server supports LITERAL+
140 * and quoted strings otherwise. (%S does not support strings that
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.
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.)
152 * Return value: %TRUE if the command was sent successfully, %FALSE if
153 * an error occurred (in which case @ex will be set).
156 camel_imap_command_start (CamelImapStore *store, CamelFolder *folder,
157 CamelException *ex, const char *fmt, ...)
164 cmd = imap_command_strdup_vprintf (store, fmt, ap);
167 CAMEL_SERVICE_REC_LOCK (store, connect_lock);
168 ok = imap_command_start (store, folder, cmd, ex);
172 CAMEL_SERVICE_REC_UNLOCK (store, connect_lock);
177 imap_command_start (CamelImapStore *store, CamelFolder *folder,
178 const char *cmd, CamelException *ex)
182 g_return_val_if_fail(store->ostream!=NULL, FALSE);
183 g_return_val_if_fail(store->istream!=NULL, FALSE);
185 /* Check for current folder */
186 if (folder && folder != store->current_folder) {
187 CamelImapResponse *response;
188 CamelException internal_ex;
190 response = camel_imap_command (store, folder, ex, NULL);
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);
202 /* Send the command */
203 if (camel_verbose_debug) {
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";
215 fprintf (stderr, "sending : %c%.5u %s\r\n", store->tag_prefix, store->command, mask);
218 nwritten = camel_stream_printf (store->ostream, "%c%.5u %s\r\n",
219 store->tag_prefix, store->command++, cmd);
221 if (nwritten == -1) {
223 camel_exception_set (ex, CAMEL_EXCEPTION_USER_CANCEL,
224 _("Operation cancelled"));
226 camel_exception_set (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
229 camel_service_disconnect (CAMEL_SERVICE (store), FALSE, NULL);
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
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.
247 * This function assumes you have an exclusive lock on the imap stream.
249 * Return value: as for camel_imap_command(). On failure, the store's
250 * connect_lock will be released.
253 camel_imap_command_continuation (CamelImapStore *store, const char *cmd,
254 size_t cmdlen, CamelException *ex)
256 if (!camel_imap_store_connected (store, ex))
259 g_return_val_if_fail(store->ostream!=NULL, NULL);
260 g_return_val_if_fail(store->istream!=NULL, NULL);
262 if (camel_stream_write (store->ostream, cmd, cmdlen) == -1 ||
263 camel_stream_write (store->ostream, "\r\n", 2) == -1) {
265 camel_exception_set (ex, CAMEL_EXCEPTION_USER_CANCEL,
266 _("Operation cancelled"));
268 camel_exception_set (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
270 camel_service_disconnect (CAMEL_SERVICE (store), FALSE, NULL);
271 CAMEL_SERVICE_REC_UNLOCK (store, connect_lock);
275 return imap_read_response (store, ex);
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
284 * This reads a single tagged, untagged, or continuation response from
285 * @store into *@response. The caller must free the string when it is
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.
293 CamelImapResponseType
294 camel_imap_command_response (CamelImapStore *store, char **response,
297 CamelImapResponseType type;
300 if (camel_imap_store_readline (store, &respbuf, ex) < 0) {
301 CAMEL_SERVICE_REC_UNLOCK (store, connect_lock);
302 return CAMEL_IMAP_RESPONSE_ERROR;
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;
316 type = CAMEL_IMAP_RESPONSE_ERROR;
320 /* Read the rest of the response. */
321 type = CAMEL_IMAP_RESPONSE_UNTAGGED;
322 respbuf = imap_read_untagged (store, respbuf, ex);
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)) {
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);
340 type = CAMEL_IMAP_RESPONSE_CONTINUATION;
343 type = CAMEL_IMAP_RESPONSE_TAGGED;
348 if (type == CAMEL_IMAP_RESPONSE_ERROR ||
349 type == CAMEL_IMAP_RESPONSE_TAGGED)
350 CAMEL_SERVICE_REC_UNLOCK (store, connect_lock);
356 imap_read_response (CamelImapStore *store, CamelException *ex)
358 CamelImapResponse *response;
359 CamelImapResponseType type;
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.
367 CAMEL_SERVICE_REC_LOCK (store, connect_lock);
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));
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);
380 if (type == CAMEL_IMAP_RESPONSE_ERROR) {
381 camel_imap_response_free_without_processing (store, response);
385 response->status = respbuf;
387 /* Check for OK or continuation response. */
390 p = strchr (respbuf, ' ');
391 if (p && !g_ascii_strncasecmp (p, " OK", 3))
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.
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",
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);
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);
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
422 imap_read_untagged (CamelImapStore *store, char *line, CamelException *ex)
424 int fulllen, ldigits, nread, n, i, sexp = 0;
428 char *end, *p, *s, *d;
430 p = strrchr (line, '{');
434 data = g_ptr_array_new ();
438 str = g_string_new (line);
441 g_ptr_array_add (data, str);
443 if (!(p = strrchr (str->str, '{')) || p[1] == '-')
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++) {
458 length = strtoul (p + 1, &end, 10);
459 if (*end != '}' || *(end + 1) || end == p + 1 || length >= UINT_MAX - 2)
461 ldigits = end - (p + 1);
463 /* Read the literal */
464 str = g_string_sized_new (length + 2);
469 if ((n = camel_stream_read (store->istream, str->str + nread + 1, length - nread)) == -1) {
471 camel_exception_set (ex, CAMEL_EXCEPTION_USER_CANCEL,
472 _("Operation cancelled"));
474 camel_exception_set (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
476 camel_service_disconnect (CAMEL_SERVICE (store), FALSE, NULL);
477 g_string_free (str, TRUE);
482 } while (n > 0 && nread < length);
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);
491 str->str[length + 1] = '\0';
493 if (camel_debug("imap")) {
494 printf("Literal: -->");
495 fwrite(str->str+1, 1, length, stdout);
499 /* Fix up the literal, turning CRLFs into LF. Also, if
500 * we find any embedded NULs, strip them. This is
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
509 s = d = str->str + 1;
510 end = str->str + 1 + length;
512 while (s < end && *s == '\0') {
516 if (*s == '\r' && *(s + 1) == '\n') {
523 str->len = length + 1;
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...
533 sprintf (p, "{%0*u}", ldigits, length);
536 g_ptr_array_add (data, str);
538 /* Read the next line. */
540 if (camel_imap_store_readline (store, &line, ex) < 0)
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);
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);
556 g_string_free (str, TRUE);
559 g_ptr_array_free (data, TRUE);
563 for (i = 0; i < data->len; i++)
564 g_string_free (data->pdata[i], TRUE);
565 g_ptr_array_free (data, TRUE);
571 * camel_imap_response_free:
572 * @store: the CamelImapStore the response is from
573 * @response: a CamelImapResponse
575 * Frees all of the data in @response and processes any untagged
576 * EXPUNGE and EXISTS responses in it. Releases @store's connect_lock.
579 camel_imap_response_free (CamelImapStore *store, CamelImapResponse *response)
581 int i, number, exists = 0;
582 GArray *expunged = NULL;
588 for (i = 0; i < response->untagged->len; i++) {
589 resp = response->untagged->pdata[i];
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")) {
596 } else if (!g_ascii_strcasecmp (p, " EXPUNGE")
597 || !g_ascii_strcasecmp(p, " XGWMOVE")) {
598 /* XGWMOVE response is the same as an EXPUNGE response */
600 expunged = g_array_new (FALSE, FALSE,
603 g_array_append_val (expunged, number);
609 g_ptr_array_free (response->untagged, TRUE);
610 g_free (response->status);
612 if (response->folder) {
613 if (exists > 0 || expunged) {
614 /* Update the summary */
615 camel_imap_folder_changed (response->folder,
616 exists, expunged, NULL);
618 g_array_free (expunged, TRUE);
621 camel_object_unref (CAMEL_OBJECT (response->folder));
625 CAMEL_SERVICE_REC_UNLOCK (store, connect_lock);
629 * camel_imap_response_free_without_processing:
630 * @store: the CamelImapStore the response is from.
631 * @response: a CamelImapResponse:
633 * Frees all of the data in @response without processing any untagged
634 * responses. Releases @store's command lock.
637 camel_imap_response_free_without_processing (CamelImapStore *store,
638 CamelImapResponse *response)
643 if (response->folder) {
644 camel_object_unref (CAMEL_OBJECT (response->folder));
645 response->folder = NULL;
647 camel_imap_response_free (store, response);
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
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.
663 * Return value: the desired response string, which the caller must free.
666 camel_imap_response_extract (CamelImapStore *store,
667 CamelImapResponse *response,
671 int len = strlen (type), i;
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);
681 resp = (char *) imap_next_word (resp);
683 if (!g_ascii_strncasecmp (resp, type, len))
687 if (i < response->untagged->len) {
688 resp = response->untagged->pdata[i];
689 g_ptr_array_remove_index (response->untagged, i);
692 camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
693 _("IMAP server response did not contain "
694 "%s information"), type);
697 camel_imap_response_free (store, response);
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
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.
712 * Return value: the desired response string, which the caller must free.
715 camel_imap_response_extract_continuation (CamelImapStore *store,
716 CamelImapResponse *response,
721 if (response->status && *response->status == '+') {
722 status = response->status;
723 response->status = NULL;
724 camel_imap_response_free (store, response);
728 camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
729 _("Unexpected OK response from IMAP server: %s"),
731 camel_imap_response_free (store, response);
736 imap_command_strdup_vprintf (CamelImapStore *store, const char *fmt,
740 const char *p, *start;
741 char *out, *outptr, *string;
742 int num, len, i, arglen;
744 args = g_ptr_array_new ();
746 /* Determine the length of the data */
750 p = strchr (start, '%');
756 num = va_arg (ap, int);
757 g_ptr_array_add (args, GINT_TO_POINTER (num));
762 string = va_arg (ap, char *);
763 g_ptr_array_add (args, string);
765 len += strlen (string);
770 string = va_arg (ap, char *);
771 /* NB: string is freed during output for %F and %G */
773 char *s = camel_imap_store_summary_full_from_path(store->summary, string);
775 string = camel_utf8_utf7(s);
778 string = camel_utf8_utf7(string);
780 } else if (*p == 'G') {
781 string = camel_utf8_utf7(string);
784 arglen = strlen (string);
785 g_ptr_array_add (args, string);
786 if (imap_is_atom (string)) {
789 if (store->capabilities & IMAP_CAPABILITY_LITERALPLUS)
800 g_warning ("camel-imap-command is not printf. I don't "
801 "know what '%%%c' means.", *p);
802 start = *p ? p + 1 : p;
807 /* Now write out the string */
808 outptr = out = g_malloc (len + 1);
812 p = strchr (start, '%');
814 strcpy (outptr, start);
817 strncpy (outptr, start, p - start);
823 num = GPOINTER_TO_INT (args->pdata[i++]);
824 outptr += sprintf (outptr, "%d", num);
828 string = args->pdata[i++];
829 outptr += sprintf (outptr, "%s", string);
834 string = args->pdata[i++];
835 if (imap_is_atom (string)) {
836 outptr += sprintf (outptr, "%s", string);
838 len = strlen (string);
839 if (len && store->capabilities & IMAP_CAPABILITY_LITERALPLUS) {
840 outptr += sprintf (outptr, "{%d+}\r\n%s", len, string);
842 char *quoted = imap_quote_string (string);
844 outptr += sprintf (outptr, "%s", quoted);
849 if (*p == 'F' || *p == 'G')
857 start = *p ? p + 1 : p;
860 g_ptr_array_free (args, TRUE);
866 imap_command_strdup_printf (CamelImapStore *store, const char *fmt, ...)
872 result = imap_command_strdup_vprintf (store, fmt, ap);