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 (C) 1999-2008 Novell, Inc. (www.novell.com)
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-imap-command.h"
39
#include "camel-imap-folder.h"
40
#include "camel-imap-store-summary.h"
41
#include "camel-imap-store.h"
42
#include "camel-imap-utils.h"
44
extern gint camel_verbose_debug;
46
static gboolean imap_command_start (CamelImapStore *store, CamelFolder *folder,
47
const gchar *cmd, GCancellable *cancellable,
49
static CamelImapResponse *imap_read_response (CamelImapStore *store,
50
GCancellable *cancellable,
52
static gchar *imap_read_untagged (CamelImapStore *store, gchar *line,
53
GCancellable *cancellable, GError **error);
54
static gchar *imap_command_strdup_vprintf (CamelImapStore *store,
55
const gchar *fmt, va_list ap);
56
static gchar *imap_command_strdup_printf (CamelImapStore *store,
57
const gchar *fmt, ...);
61
* @store: the IMAP store
62
* @folder: The folder to perform the operation in (or %NULL if not
64
* @cancellable: optional #GCancellable object, or %NULL
65
* @error: return location for a #GError, or %NULL
66
* @fmt: a sort of printf-style format string, followed by arguments
68
* This function calls camel_imap_command_start() to send the
69
* command, then reads the complete response to it using
70
* camel_imap_command_response() and returns a CamelImapResponse
73
* As a special case, if @fmt is %NULL, it will just select @folder
74
* and return the response from doing so.
76
* See camel_imap_command_start() for details on @fmt.
78
* On success, the store's connect_lock will be locked. It will be freed
79
* when you call camel_imap_response_free. (The lock is recursive, so
80
* callers can grab and release it themselves if they need to run
81
* multiple commands atomically.)
83
* Returns: %NULL if an error occurred (in which case @ex will
84
* be set). Otherwise, a CamelImapResponse describing the server's
85
* response, which the caller must free with camel_imap_response_free().
88
camel_imap_command (CamelImapStore *store,
90
GCancellable *cancellable,
92
const gchar *fmt, ...)
97
camel_service_lock (CAMEL_SERVICE (store), CAMEL_SERVICE_REC_CONNECT_LOCK);
101
cmd = imap_command_strdup_vprintf (store, fmt, ap);
104
const gchar *full_name;
106
g_object_ref (folder);
107
if (store->current_folder)
108
g_object_unref (store->current_folder);
109
store->current_folder = folder;
111
full_name = camel_folder_get_full_name (folder);
112
cmd = imap_command_strdup_printf (store, "SELECT %F", full_name);
115
if (!imap_command_start (store, folder, cmd, cancellable, error)) {
117
camel_service_unlock (CAMEL_SERVICE (store), CAMEL_SERVICE_REC_CONNECT_LOCK);
122
return imap_read_response (store, cancellable, error);
126
* camel_imap_command_start:
127
* @store: the IMAP store
128
* @folder: The folder to perform the operation in (or %NULL if not
130
* @cancellable: optional #GCancellable object, or %NULL
131
* @error: return location for a #GError, or %NULL
132
* @fmt: a sort of printf-style format string, followed by arguments
134
* This function makes sure that @folder (if non-%NULL) is the
135
* currently-selected folder on @store and then sends the IMAP command
136
* specified by @fmt and the following arguments.
138
* @fmt can include the following %-escapes ONLY:
139
* %s, %d, %%: as with printf
140
* %S: an IMAP "string" (quoted string or literal)
141
* %F: an IMAP folder name
142
* %G: an IMAP folder name, with namespace already prepended
144
* %S strings will be passed as literals if the server supports LITERAL+
145
* and quoted strings otherwise. (%S does not support strings that
148
* %F will have the imap store's namespace prepended; %F and %G will then
149
* be converted to UTF-7 and processed like %S.
151
* On success, the store's connect_lock will be locked. It will be
152
* freed when %CAMEL_IMAP_RESPONSE_TAGGED or %CAMEL_IMAP_RESPONSE_ERROR
153
* is returned from camel_imap_command_response(). (The lock is
154
* recursive, so callers can grab and release it themselves if they
155
* need to run multiple commands atomically.)
157
* Returns: %TRUE if the command was sent successfully, %FALSE if
158
* an error occurred (in which case @ex will be set).
161
camel_imap_command_start (CamelImapStore *store,
163
GCancellable *cancellable,
165
const gchar *fmt, ...)
172
cmd = imap_command_strdup_vprintf (store, fmt, ap);
175
camel_service_lock (CAMEL_SERVICE (store), CAMEL_SERVICE_REC_CONNECT_LOCK);
176
ok = imap_command_start (store, folder, cmd, cancellable, error);
180
camel_service_unlock (CAMEL_SERVICE (store), CAMEL_SERVICE_REC_CONNECT_LOCK);
185
imap_command_start (CamelImapStore *store,
188
GCancellable *cancellable,
193
if (!store->ostream) {
195
error, CAMEL_STORE_ERROR,
196
CAMEL_STORE_ERROR_INVALID,
197
_("No output stream"));
201
if (!store->istream) {
203
error, CAMEL_STORE_ERROR,
204
CAMEL_STORE_ERROR_INVALID,
205
_("No input stream"));
209
/* Check for current folder */
210
if (folder && folder != store->current_folder) {
211
CamelImapResponse *response;
212
GError *local_error = NULL;
214
response = camel_imap_command (
215
store, folder, cancellable, error, NULL);
219
/* FIXME Pass a GCancellable */
220
camel_imap_folder_selected (
221
folder, response, NULL, &local_error);
222
camel_imap_response_free (store, response);
224
if (local_error != NULL) {
225
g_propagate_error (error, local_error);
230
/* Send the command */
231
if (camel_verbose_debug) {
234
if (!strncmp ("LOGIN \"", cmd, 7))
235
mask = "LOGIN \"xxx\" xxx";
236
else if (!strncmp ("LOGIN {", cmd, 7))
237
mask = "LOGIN {N+}\r\nxxx {N+}\r\nxxx";
238
else if (!strncmp ("LOGIN ", cmd, 6))
239
mask = "LOGIN xxx xxx";
243
fprintf (stderr, "sending : %c%.5u %s\r\n", store->tag_prefix, store->command, mask);
246
nwritten = camel_stream_printf (
247
store->ostream, "%c%.5u %s\r\n",
248
store->tag_prefix, store->command++, cmd);
250
if (nwritten == -1) {
254
G_IO_ERROR_CANCELLED,
255
_("Operation cancelled"));
259
g_io_error_from_errno (errno),
260
"%s", g_strerror (errno));
262
camel_service_disconnect_sync (
263
CAMEL_SERVICE (store), FALSE, NULL);
272
* camel_imap_command_continuation:
273
* @store: the IMAP store
274
* @cmd: buffer containing the response/request data
275
* @cmdlen: command length
276
* @error: return location for a #GError, or %NULL
278
* This method is for sending continuing responses to the IMAP server
279
* after camel_imap_command() or camel_imap_command_response() returns
280
* a continuation response.
282
* This function assumes you have an exclusive lock on the imap stream.
284
* Returns: as for camel_imap_command(). On failure, the store's
285
* connect_lock will be released.
288
camel_imap_command_continuation (CamelImapStore *store,
291
GCancellable *cancellable,
294
if (!camel_imap_store_connected (store, error))
297
if (!store->ostream) {
299
error, CAMEL_STORE_ERROR,
300
CAMEL_STORE_ERROR_INVALID,
301
_("No output stream"));
305
if (!store->istream) {
307
error, CAMEL_STORE_ERROR,
308
CAMEL_STORE_ERROR_INVALID,
309
_("No input stream"));
313
if (camel_stream_write (store->ostream, cmd, cmdlen, cancellable, error) == -1 ||
314
camel_stream_write (store->ostream, "\r\n", 2, cancellable, error) == -1) {
315
camel_service_disconnect_sync (
316
CAMEL_SERVICE (store), FALSE, NULL);
317
camel_service_unlock (
318
CAMEL_SERVICE (store),
319
CAMEL_SERVICE_REC_CONNECT_LOCK);
323
return imap_read_response (store, cancellable, error);
327
* camel_imap_command_response:
328
* @store: the IMAP store
329
* @response: a pointer to pass back the response data in
330
* @cancellable: optional #GCancellable object, or %NULL
331
* @error: return location for a #GError, or %NULL
333
* This reads a single tagged, untagged, or continuation response from
334
* @store into *@response. The caller must free the string when it is
337
* Returns: One of %CAMEL_IMAP_RESPONSE_CONTINUATION,
338
* %CAMEL_IMAP_RESPONSE_UNTAGGED, %CAMEL_IMAP_RESPONSE_TAGGED, or
339
* %CAMEL_IMAP_RESPONSE_ERROR. If either of the last two, @store's
340
* command lock will be unlocked.
342
CamelImapResponseType
343
camel_imap_command_response (CamelImapStore *store,
345
GCancellable *cancellable,
348
CamelImapResponseType type;
351
if (camel_imap_store_readline (store, &respbuf, cancellable, error) < 0) {
352
camel_service_unlock (CAMEL_SERVICE (store), CAMEL_SERVICE_REC_CONNECT_LOCK);
353
return CAMEL_IMAP_RESPONSE_ERROR;
358
if (!g_ascii_strncasecmp (respbuf, "* BYE", 5)) {
359
const gchar *err = NULL;
361
if (respbuf [5] && g_ascii_strncasecmp (respbuf + 6, "[ALERT] ", 8) == 0)
365
err = g_strerror (104);
367
/* Connection was lost, no more data to fetch */
368
camel_service_disconnect_sync (
369
CAMEL_SERVICE (store), FALSE, NULL);
371
error, CAMEL_SERVICE_ERROR,
372
CAMEL_SERVICE_ERROR_UNAVAILABLE,
373
_("Server unexpectedly disconnected: %s"), err);
374
store->connected = FALSE;
377
type = CAMEL_IMAP_RESPONSE_ERROR;
381
/* Read the rest of the response. */
382
type = CAMEL_IMAP_RESPONSE_UNTAGGED;
383
respbuf = imap_read_untagged (
384
store, respbuf, cancellable, error);
386
type = CAMEL_IMAP_RESPONSE_ERROR;
387
else if (!g_ascii_strncasecmp (respbuf, "* OK [ALERT]", 12)
388
|| !g_ascii_strncasecmp (respbuf, "* NO [ALERT]", 12)
389
|| !g_ascii_strncasecmp (respbuf, "* BAD [ALERT]", 13)) {
390
CamelService *service;
391
CamelSession *session;
395
/* for imap ALERT codes, account user@host */
396
/* we might get a ']' from a BAD response since we +12,
398
service = CAMEL_SERVICE (store);
399
url = camel_service_get_camel_url (service);
400
session = camel_service_get_session (service);
402
msg = g_strdup_printf (
403
_("Alert from IMAP server %s@%s:\n%s"),
404
url->user, url->host, respbuf + 12);
405
camel_session_alert_user (
406
session, CAMEL_SESSION_ALERT_WARNING,
413
type = CAMEL_IMAP_RESPONSE_CONTINUATION;
416
type = CAMEL_IMAP_RESPONSE_TAGGED;
421
if (type == CAMEL_IMAP_RESPONSE_ERROR ||
422
type == CAMEL_IMAP_RESPONSE_TAGGED)
423
camel_service_unlock (CAMEL_SERVICE (store), CAMEL_SERVICE_REC_CONNECT_LOCK);
428
static CamelImapResponse *
429
imap_read_response (CamelImapStore *store,
430
GCancellable *cancellable,
433
CamelImapResponse *response;
434
CamelImapResponseType type;
437
/* Get another lock so that when we reach the tagged
438
* response and camel_imap_command_response unlocks,
439
* we're still locked. This lock is owned by response
440
* and gets unlocked when response is freed.
442
camel_service_lock (CAMEL_SERVICE (store), CAMEL_SERVICE_REC_CONNECT_LOCK);
444
response = g_new0 (CamelImapResponse, 1);
445
/*FIXME if (store->current_folder && camel_disco_store_status (CAMEL_DISCO_STORE (store)) != CAMEL_DISCO_STORE_RESYNCING) {
446
response->folder = store->current_folder;
447
g_object_ref (response->folder);
450
response->untagged = g_ptr_array_new ();
451
while ((type = camel_imap_command_response (
452
store, &respbuf, cancellable, error))
453
== CAMEL_IMAP_RESPONSE_UNTAGGED)
454
g_ptr_array_add (response->untagged, respbuf);
456
if (type == CAMEL_IMAP_RESPONSE_ERROR) {
457
camel_imap_response_free_without_processing (store, response);
461
response->status = respbuf;
463
/* Check for OK or continuation response. */
466
p = strchr (respbuf, ' ');
467
if (p && !g_ascii_strncasecmp (p, " OK", 3))
470
/* We should never get BAD, or anything else but +, OK, or NO
471
* for that matter. Well, we could get BAD, treat as NO.
473
if (!p || (g_ascii_strncasecmp(p, " NO", 3) != 0 && g_ascii_strncasecmp(p, " BAD", 4)) ) {
474
g_warning ("Unexpected response from IMAP server: %s",
477
error, CAMEL_SERVICE_ERROR,
478
CAMEL_SERVICE_ERROR_UNAVAILABLE,
479
_("Unexpected response from IMAP server: %s"),
481
camel_imap_response_free_without_processing (store, response);
489
error, CAMEL_SERVICE_ERROR,
490
CAMEL_SERVICE_ERROR_INVALID,
491
_("IMAP command failed: %s"),
492
(p != NULL) ? p : _("Unknown error"));
493
camel_imap_response_free_without_processing (store, response);
497
/* Given a line that is the start of an untagged response, read and
498
* return the complete response, which may include an arbitrary number
502
imap_read_untagged (CamelImapStore *store,
504
GCancellable *cancellable,
507
gint fulllen, ldigits, nread, n, i, sexp = 0;
511
gchar *end, *p, *s, *d;
513
p = strrchr (line, '{');
517
data = g_ptr_array_new ();
521
str = g_string_new (line);
524
g_ptr_array_add (data, str);
526
if (!(p = strrchr (str->str, '{')) || p[1] == '-')
529
/* HACK ALERT: We scan the non-literal part of the string, looking for possible s expression braces.
530
This assumes we're getting s-expressions, which we should be.
531
This is so if we get a blank line after a literal, in an s-expression, we can keep going, since
532
we do no other parsing at this level.
533
TODO: handle quoted strings? */
534
for (s=str->str; s<p; s++) {
541
length = strtoul (p + 1, &end, 10);
542
if (*end != '}' || *(end + 1) || end == p + 1 || length >= UINT_MAX - 2)
544
ldigits = end - (p + 1);
546
/* Read the literal */
547
str = g_string_sized_new (length + 2);
552
n = camel_stream_read (
554
str->str + nread + 1,
558
camel_service_disconnect_sync (
559
CAMEL_SERVICE (store), FALSE, NULL);
560
g_string_free (str, TRUE);
565
} while (n > 0 && nread < length);
567
if (nread < length) {
569
error, CAMEL_SERVICE_ERROR,
570
CAMEL_SERVICE_ERROR_UNAVAILABLE,
571
_("Server response ended too soon."));
572
camel_service_disconnect_sync (
573
CAMEL_SERVICE (store), FALSE, NULL);
574
g_string_free (str, TRUE);
577
str->str[length + 1] = '\0';
579
if (camel_debug("imap")) {
580
printf("Literal: -->");
581
fwrite (str->str+1, 1, length, stdout);
585
/* Fix up the literal, turning CRLFs into LF. Also, if
586
* we find any embedded NULs, strip them. This is
588
* - The IMAP grammar says you can't have NULs here
589
* anyway, so this will not affect our behavior
590
* against any completely correct server.
591
* - WU-imapd 12.264 (at least) will cheerily pass
592
* NULs along if they are embedded in the message
595
s = d = str->str + 1;
596
end = str->str + 1 + length;
598
while (s < end && *s == '\0') {
602
if (*s == '\r' && *(s + 1) == '\n') {
609
str->len = length + 1;
611
/* p points to the "{" in the line that starts the
612
* literal. The length of the CR-less response must be
613
* less than or equal to the length of the response
614
* with CRs, therefore overwriting the old value with
615
* the new value cannot cause an overrun. However, we
616
* don't want it to be shorter either, because then the
617
* GString's length would be off...
619
sprintf (p, "{%0*u}", ldigits, length);
622
g_ptr_array_add (data, str);
624
/* Read the next line. */
626
if (camel_imap_store_readline (store, &line, cancellable, error) < 0)
629
/* MAJOR HACK ALERT, gropuwise sometimes sends an extra blank line after literals, check that here
630
But only do it if we're inside an sexpression */
631
if (line[0] == 0 && sexp > 0)
632
g_warning("Server sent empty line after a literal, assuming in error");
633
} while (line[0] == 0 && sexp > 0);
636
/* Now reassemble the data. */
637
p = line = g_malloc (fulllen + 1);
638
for (i = 0; i < data->len; i++) {
639
str = data->pdata[i];
640
memcpy (p, str->str, str->len);
642
g_string_free (str, TRUE);
645
g_ptr_array_free (data, TRUE);
649
for (i = 0; i < data->len; i++)
650
g_string_free (data->pdata[i], TRUE);
651
g_ptr_array_free (data, TRUE);
656
* camel_imap_response_free:
657
* @store: the CamelImapStore the response is from
658
* @response: a CamelImapResponse
660
* Frees all of the data in @response and processes any untagged
661
* EXPUNGE and EXISTS responses in it. Releases @store's connect_lock.
664
camel_imap_response_free (CamelImapStore *store, CamelImapResponse *response)
666
gint i, number, exists = 0;
667
GArray *expunged = NULL;
673
for (i = 0; i < response->untagged->len; i++) {
674
resp = response->untagged->pdata[i];
676
if (response->folder) {
677
/* Check if it's something we need to handle. */
678
number = strtoul (resp + 2, &p, 10);
679
if (!g_ascii_strcasecmp (p, " EXISTS")) {
681
} else if (!g_ascii_strcasecmp (p, " EXPUNGE")
682
|| !g_ascii_strcasecmp(p, " XGWMOVE")) {
683
/* XGWMOVE response is the same as an EXPUNGE response */
685
expunged = g_array_new (FALSE, FALSE,
688
g_array_append_val (expunged, number);
694
g_ptr_array_free (response->untagged, TRUE);
695
g_free (response->status);
697
if (response->folder) {
698
if (exists > 0 || expunged) {
699
/* Update the summary */
700
/* FIXME Pass a GCancellable */
701
camel_imap_folder_changed (
702
response->folder, exists,
703
expunged, NULL, NULL);
705
g_array_free (expunged, TRUE);
708
g_object_unref (response->folder);
712
camel_service_unlock (CAMEL_SERVICE (store), CAMEL_SERVICE_REC_CONNECT_LOCK);
716
* camel_imap_response_free_without_processing:
717
* @store: the CamelImapStore the response is from.
718
* @response: a CamelImapResponse:
720
* Frees all of the data in @response without processing any untagged
721
* responses. Releases @store's command lock.
724
camel_imap_response_free_without_processing (CamelImapStore *store,
725
CamelImapResponse *response)
730
if (response->folder) {
731
g_object_unref (response->folder);
732
response->folder = NULL;
734
camel_imap_response_free (store, response);
738
* camel_imap_response_extract:
739
* @store: the store the response came from
740
* @response: the response data returned from camel_imap_command
741
* @type: the response type to extract
742
* @error: return location for a #GError, or %NULL
744
* This checks that @response contains a single untagged response of
745
* type @type and returns just that response data. If @response
746
* doesn't contain the right information, the function will set @ex
747
* and return %NULL. Either way, @response will be freed and the
748
* store's connect_lock released.
750
* Returns: the desired response string, which the caller must free.
753
camel_imap_response_extract (CamelImapStore *store,
754
CamelImapResponse *response,
763
for (i = 0; i < response->untagged->len; i++) {
764
resp = response->untagged->pdata[i];
765
/* Skip "* ", and initial sequence number, if present */
766
strtoul (resp + 2, &resp, 10);
768
resp = (gchar *) imap_next_word (resp);
770
if (!g_ascii_strncasecmp (resp, type, len))
774
if (i < response->untagged->len) {
775
resp = response->untagged->pdata[i];
776
g_ptr_array_remove_index (response->untagged, i);
780
error, CAMEL_SERVICE_ERROR,
781
CAMEL_SERVICE_ERROR_UNAVAILABLE,
782
_("IMAP server response did not "
783
"contain %s information"), type);
786
camel_imap_response_free (store, response);
791
* camel_imap_response_extract_continuation:
792
* @store: the store the response came from
793
* @response: the response data returned from camel_imap_command
794
* @error: return location for a #GError, or %NULL
796
* This checks that @response contains a continuation response, and
797
* returns just that data. If @response doesn't contain a continuation
798
* response, the function will set @ex, release @store's connect_lock,
799
* and return %NULL. Either way, @response will be freed.
801
* Returns: the desired response string, which the caller must free.
804
camel_imap_response_extract_continuation (CamelImapStore *store,
805
CamelImapResponse *response,
810
if (response->status && *response->status == '+') {
811
status = response->status;
812
response->status = NULL;
813
camel_imap_response_free (store, response);
818
error, CAMEL_SERVICE_ERROR,
819
CAMEL_SERVICE_ERROR_UNAVAILABLE,
820
_("Unexpected OK response from IMAP server: %s"),
823
camel_imap_response_free (store, response);
828
imap_command_strdup_vprintf (CamelImapStore *store, const gchar *fmt,
832
const gchar *p, *start;
833
gchar *out, *outptr, *string;
834
gint num, len, i, arglen;
836
args = g_ptr_array_new ();
838
/* Determine the length of the data */
842
p = strchr (start, '%');
848
num = va_arg (ap, gint);
849
g_ptr_array_add (args, GINT_TO_POINTER (num));
854
string = va_arg (ap, gchar *);
855
g_ptr_array_add (args, string);
857
len += strlen (string);
862
string = va_arg (ap, gchar *);
863
/* NB: string is freed during output for %F and %G */
865
gchar *s = camel_imap_store_summary_full_from_path (store->summary, string);
867
string = camel_utf8_utf7 (s);
870
string = camel_utf8_utf7 (string);
872
} else if (*p == 'G') {
873
string = camel_utf8_utf7 (string);
876
arglen = strlen (string);
877
g_ptr_array_add (args, string);
878
if (imap_is_atom (string)) {
881
if (store->capabilities & IMAP_CAPABILITY_LITERALPLUS)
892
g_warning ("camel-imap-command is not printf. I don't "
893
"know what '%%%c' means.", *p);
894
start = *p ? p + 1 : p;
899
/* Now write out the string */
900
outptr = out = g_malloc (len + 1);
904
p = strchr (start, '%');
906
strcpy (outptr, start);
909
strncpy (outptr, start, p - start);
915
num = GPOINTER_TO_INT (args->pdata[i++]);
916
outptr += sprintf (outptr, "%d", num);
920
string = args->pdata[i++];
921
outptr += sprintf (outptr, "%s", string);
926
string = args->pdata[i++];
927
if (imap_is_atom (string)) {
928
outptr += sprintf (outptr, "%s", string);
930
len = strlen (string);
931
if (len && store->capabilities & IMAP_CAPABILITY_LITERALPLUS) {
932
outptr += sprintf (outptr, "{%d+}\r\n%s", len, string);
934
gchar *quoted = imap_quote_string (string);
936
outptr += sprintf (outptr, "%s", quoted);
941
if (*p == 'F' || *p == 'G')
949
start = *p ? p + 1 : p;
952
g_ptr_array_free (args, TRUE);
958
imap_command_strdup_printf (CamelImapStore *store, const gchar *fmt, ...)
964
result = imap_command_strdup_vprintf (store, fmt, ap);