2003-06-16 Not Zed <NotZed@Ximian.com> ** See bug #44322 * providers/imap/camel-imap-command.c (imap_command_strdup_vprintf): If we are outputting a folder name, make sure we calculate buffer size based on the raw/utf7 version ** See bug #44121 * camel-multipart-signed.c (signed_get_part): If we can't parse the content, but we have a stream, just use that as the content. svn path=/trunk/; revision=21454
823 lines
22 KiB
C
823 lines
22 KiB
C
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
|
|
/* camel-imap-command.c: IMAP command sending/parsing routines */
|
|
|
|
/*
|
|
* Authors:
|
|
* Dan Winship <danw@ximian.com>
|
|
* Jeffrey Stedfast <fejj@ximian.com>
|
|
*
|
|
* Copyright 2000, 2001 Ximian, Inc.
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of version 2 of the GNU General Public
|
|
* License as published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public
|
|
* License along with this program; if not, write to the
|
|
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
|
* Boston, MA 02111-1307, USA.
|
|
*
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include <config.h>
|
|
#endif
|
|
|
|
#include <stdarg.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
|
|
#include "camel-imap-command.h"
|
|
#include "camel-imap-utils.h"
|
|
#include "camel-imap-folder.h"
|
|
#include "camel-imap-store.h"
|
|
#include "camel-imap-store-summary.h"
|
|
#include "camel-imap-private.h"
|
|
#include <camel/camel-exception.h>
|
|
#include <camel/camel-private.h>
|
|
#include <camel/camel-utf8.h>
|
|
#include <camel/camel-session.h>
|
|
|
|
|
|
extern int camel_verbose_debug;
|
|
|
|
static gboolean imap_command_start (CamelImapStore *store, CamelFolder *folder,
|
|
const char *cmd, CamelException *ex);
|
|
CamelImapResponse *imap_read_response (CamelImapStore *store,
|
|
CamelException *ex);
|
|
static char *imap_read_untagged (CamelImapStore *store, char *line,
|
|
CamelException *ex);
|
|
static char *imap_command_strdup_vprintf (CamelImapStore *store,
|
|
const char *fmt, va_list ap);
|
|
static char *imap_command_strdup_printf (CamelImapStore *store,
|
|
const char *fmt, ...);
|
|
|
|
/**
|
|
* camel_imap_command:
|
|
* @store: the IMAP store
|
|
* @folder: The folder to perform the operation in (or %NULL if not
|
|
* relevant).
|
|
* @ex: a CamelException
|
|
* @fmt: a sort of printf-style format string, followed by arguments
|
|
*
|
|
* This function calls camel_imap_command_start() to send the
|
|
* command, then reads the complete response to it using
|
|
* camel_imap_command_response() and returns a CamelImapResponse
|
|
* structure.
|
|
*
|
|
* As a special case, if @fmt is %NULL, it will just select @folder
|
|
* and return the response from doing so.
|
|
*
|
|
* See camel_imap_command_start() for details on @fmt.
|
|
*
|
|
* On success, the store's connect_lock will be locked. It will be freed
|
|
* when you call camel_imap_response_free. (The lock is recursive, so
|
|
* callers can grab and release it themselves if they need to run
|
|
* multiple commands atomically.)
|
|
*
|
|
* Return value: %NULL if an error occurred (in which case @ex will
|
|
* be set). Otherwise, a CamelImapResponse describing the server's
|
|
* response, which the caller must free with camel_imap_response_free().
|
|
**/
|
|
CamelImapResponse *
|
|
camel_imap_command (CamelImapStore *store, CamelFolder *folder,
|
|
CamelException *ex, const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
char *cmd;
|
|
|
|
CAMEL_SERVICE_LOCK (store, connect_lock);
|
|
|
|
if (fmt) {
|
|
va_start (ap, fmt);
|
|
cmd = imap_command_strdup_vprintf (store, fmt, ap);
|
|
va_end (ap);
|
|
} else {
|
|
if (store->current_folder) {
|
|
camel_object_unref (CAMEL_OBJECT (store->current_folder));
|
|
store->current_folder = NULL;
|
|
}
|
|
store->current_folder = folder;
|
|
camel_object_ref (CAMEL_OBJECT (folder));
|
|
cmd = imap_command_strdup_printf (store, "SELECT %F",
|
|
folder->full_name);
|
|
}
|
|
|
|
if (!imap_command_start (store, folder, cmd, ex)) {
|
|
g_free (cmd);
|
|
CAMEL_SERVICE_UNLOCK (store, connect_lock);
|
|
return NULL;
|
|
}
|
|
g_free (cmd);
|
|
|
|
return imap_read_response (store, ex);
|
|
}
|
|
|
|
/**
|
|
* camel_imap_command_start:
|
|
* @store: the IMAP store
|
|
* @folder: The folder to perform the operation in (or %NULL if not
|
|
* relevant).
|
|
* @ex: a CamelException
|
|
* @fmt: a sort of printf-style format string, followed by arguments
|
|
*
|
|
* This function makes sure that @folder (if non-%NULL) is the
|
|
* currently-selected folder on @store and then sends the IMAP command
|
|
* specified by @fmt and the following arguments.
|
|
*
|
|
* @fmt can include the following %-escapes ONLY:
|
|
* %s, %d, %%: as with printf
|
|
* %S: an IMAP "string" (quoted string or literal)
|
|
* %F: an IMAP folder name
|
|
*
|
|
* %S strings will be passed as literals if the server supports LITERAL+
|
|
* and quoted strings otherwise. (%S does not support strings that
|
|
* contain newlines.)
|
|
*
|
|
* %F will have the imap store's namespace prepended and then be processed
|
|
* like %S.
|
|
*
|
|
* On success, the store's connect_lock will be locked. It will be
|
|
* freed when %CAMEL_IMAP_RESPONSE_TAGGED or %CAMEL_IMAP_RESPONSE_ERROR
|
|
* is returned from camel_imap_command_response(). (The lock is
|
|
* recursive, so callers can grab and release it themselves if they
|
|
* need to run multiple commands atomically.)
|
|
*
|
|
* Return value: %TRUE if the command was sent successfully, %FALSE if
|
|
* an error occurred (in which case @ex will be set).
|
|
**/
|
|
gboolean
|
|
camel_imap_command_start (CamelImapStore *store, CamelFolder *folder,
|
|
CamelException *ex, const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
char *cmd;
|
|
gboolean ok;
|
|
|
|
va_start (ap, fmt);
|
|
cmd = imap_command_strdup_vprintf (store, fmt, ap);
|
|
va_end (ap);
|
|
|
|
CAMEL_SERVICE_LOCK (store, connect_lock);
|
|
ok = imap_command_start (store, folder, cmd, ex);
|
|
g_free (cmd);
|
|
|
|
if (!ok)
|
|
CAMEL_SERVICE_UNLOCK (store, connect_lock);
|
|
return ok;
|
|
}
|
|
|
|
static gboolean
|
|
imap_command_start (CamelImapStore *store, CamelFolder *folder,
|
|
const char *cmd, CamelException *ex)
|
|
{
|
|
ssize_t nwritten;
|
|
|
|
/* Check for current folder */
|
|
if (folder && folder != store->current_folder) {
|
|
CamelImapResponse *response;
|
|
CamelException internal_ex;
|
|
|
|
response = camel_imap_command (store, folder, ex, NULL);
|
|
if (!response)
|
|
return FALSE;
|
|
camel_exception_init (&internal_ex);
|
|
camel_imap_folder_selected (folder, response, &internal_ex);
|
|
camel_imap_response_free (store, response);
|
|
if (camel_exception_is_set (&internal_ex)) {
|
|
camel_exception_xfer (ex, &internal_ex);
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/* Send the command */
|
|
if (camel_verbose_debug) {
|
|
const char *mask;
|
|
|
|
if (!strncmp ("LOGIN \"", cmd, 7))
|
|
mask = "LOGIN \"xxx\" xxx";
|
|
else if (!strncmp ("LOGIN {", cmd, 7))
|
|
mask = "LOGIN {N+}\r\nxxx {N+}\r\nxxx";
|
|
else if (!strncmp ("LOGIN ", cmd, 6))
|
|
mask = "LOGIN xxx xxx";
|
|
else
|
|
mask = cmd;
|
|
|
|
fprintf (stderr, "sending : %c%.5d %s\r\n", store->tag_prefix, store->command, mask);
|
|
}
|
|
|
|
nwritten = camel_stream_printf (store->ostream, "%c%.5d %s\r\n",
|
|
store->tag_prefix, store->command++, cmd);
|
|
|
|
if (nwritten == -1) {
|
|
if (errno == EINTR)
|
|
camel_exception_set (ex, CAMEL_EXCEPTION_USER_CANCEL,
|
|
_("Operation cancelled"));
|
|
else
|
|
camel_exception_set (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
|
|
g_strerror (errno));
|
|
|
|
camel_service_disconnect (CAMEL_SERVICE (store), FALSE, NULL);
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* camel_imap_command_continuation:
|
|
* @store: the IMAP store
|
|
* @cmd: buffer containing the response/request data
|
|
* @cmdlen: command length
|
|
* @ex: a CamelException
|
|
*
|
|
* This method is for sending continuing responses to the IMAP server
|
|
* after camel_imap_command() or camel_imap_command_response() returns
|
|
* a continuation response.
|
|
*
|
|
* This function assumes you have an exclusive lock on the imap stream.
|
|
*
|
|
* Return value: as for camel_imap_command(). On failure, the store's
|
|
* connect_lock will be released.
|
|
**/
|
|
CamelImapResponse *
|
|
camel_imap_command_continuation (CamelImapStore *store, const char *cmd,
|
|
size_t cmdlen, CamelException *ex)
|
|
{
|
|
if (!camel_imap_store_connected (store, ex))
|
|
return NULL;
|
|
|
|
if (camel_stream_write (store->ostream, cmd, cmdlen) == -1 ||
|
|
camel_stream_write (store->ostream, "\r\n", 2) == -1) {
|
|
if (errno == EINTR)
|
|
camel_exception_set (ex, CAMEL_EXCEPTION_USER_CANCEL,
|
|
_("Operation cancelled"));
|
|
else
|
|
camel_exception_set (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
|
|
g_strerror (errno));
|
|
camel_service_disconnect (CAMEL_SERVICE (store), FALSE, NULL);
|
|
CAMEL_SERVICE_UNLOCK (store, connect_lock);
|
|
return NULL;
|
|
}
|
|
|
|
return imap_read_response (store, ex);
|
|
}
|
|
|
|
/**
|
|
* camel_imap_command_response:
|
|
* @store: the IMAP store
|
|
* @response: a pointer to pass back the response data in
|
|
* @ex: a CamelException
|
|
*
|
|
* This reads a single tagged, untagged, or continuation response from
|
|
* @store into *@response. The caller must free the string when it is
|
|
* done with it.
|
|
*
|
|
* Return value: One of %CAMEL_IMAP_RESPONSE_CONTINUATION,
|
|
* %CAMEL_IMAP_RESPONSE_UNTAGGED, %CAMEL_IMAP_RESPONSE_TAGGED, or
|
|
* %CAMEL_IMAP_RESPONSE_ERROR. If either of the last two, @store's
|
|
* command lock will be unlocked.
|
|
**/
|
|
CamelImapResponseType
|
|
camel_imap_command_response (CamelImapStore *store, char **response,
|
|
CamelException *ex)
|
|
{
|
|
CamelImapResponseType type;
|
|
char *respbuf;
|
|
|
|
if (camel_imap_store_readline (store, &respbuf, ex) < 0) {
|
|
CAMEL_SERVICE_UNLOCK (store, connect_lock);
|
|
return CAMEL_IMAP_RESPONSE_ERROR;
|
|
}
|
|
|
|
switch (*respbuf) {
|
|
case '*':
|
|
if (!strncasecmp (respbuf, "* BYE", 5)) {
|
|
/* Connection was lost, no more data to fetch */
|
|
camel_service_disconnect (CAMEL_SERVICE (store), FALSE, NULL);
|
|
camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
|
|
_("Server unexpectedly disconnected: %s"),
|
|
_("Unknown error")); /* g_strerror (104)); FIXME after 1.0 is released */
|
|
store->connected = FALSE;
|
|
g_free (respbuf);
|
|
respbuf = NULL;
|
|
type = CAMEL_IMAP_RESPONSE_ERROR;
|
|
break;
|
|
}
|
|
|
|
/* Read the rest of the response. */
|
|
type = CAMEL_IMAP_RESPONSE_UNTAGGED;
|
|
respbuf = imap_read_untagged (store, respbuf, ex);
|
|
if (!respbuf)
|
|
type = CAMEL_IMAP_RESPONSE_ERROR;
|
|
else if (!strncasecmp (respbuf, "* OK [ALERT]", 12)) {
|
|
char *msg;
|
|
|
|
/* for imap ALERT codes, account user@host */
|
|
msg = g_strdup_printf(_("Alert from IMAP server %s@%s:\n%s"),
|
|
((CamelService *)store)->url->user, ((CamelService *)store)->url->host, respbuf+12);
|
|
camel_session_alert_user(((CamelService *)store)->session, CAMEL_SESSION_ALERT_WARNING, msg, FALSE);
|
|
g_free(msg);
|
|
}
|
|
|
|
break;
|
|
case '+':
|
|
type = CAMEL_IMAP_RESPONSE_CONTINUATION;
|
|
break;
|
|
default:
|
|
type = CAMEL_IMAP_RESPONSE_TAGGED;
|
|
break;
|
|
}
|
|
*response = respbuf;
|
|
|
|
if (type == CAMEL_IMAP_RESPONSE_ERROR ||
|
|
type == CAMEL_IMAP_RESPONSE_TAGGED)
|
|
CAMEL_SERVICE_UNLOCK (store, connect_lock);
|
|
|
|
return type;
|
|
}
|
|
|
|
CamelImapResponse *
|
|
imap_read_response (CamelImapStore *store, CamelException *ex)
|
|
{
|
|
CamelImapResponse *response;
|
|
CamelImapResponseType type;
|
|
char *respbuf, *p;
|
|
|
|
/* Get another lock so that when we reach the tagged
|
|
* response and camel_imap_command_response unlocks,
|
|
* we're still locked. This lock is owned by response
|
|
* and gets unlocked when response is freed.
|
|
*/
|
|
CAMEL_SERVICE_LOCK (store, connect_lock);
|
|
|
|
response = g_new0 (CamelImapResponse, 1);
|
|
if (store->current_folder && camel_disco_store_status (CAMEL_DISCO_STORE (store)) != CAMEL_DISCO_STORE_RESYNCING) {
|
|
response->folder = store->current_folder;
|
|
camel_object_ref (CAMEL_OBJECT (response->folder));
|
|
}
|
|
|
|
response->untagged = g_ptr_array_new ();
|
|
while ((type = camel_imap_command_response (store, &respbuf, ex))
|
|
== CAMEL_IMAP_RESPONSE_UNTAGGED)
|
|
g_ptr_array_add (response->untagged, respbuf);
|
|
|
|
if (type == CAMEL_IMAP_RESPONSE_ERROR) {
|
|
camel_imap_response_free_without_processing (store, response);
|
|
return NULL;
|
|
}
|
|
|
|
response->status = respbuf;
|
|
|
|
/* Check for OK or continuation response. */
|
|
if (*respbuf == '+')
|
|
return response;
|
|
p = strchr (respbuf, ' ');
|
|
if (p && !strncasecmp (p, " OK", 3))
|
|
return response;
|
|
|
|
/* We should never get BAD, or anything else but +, OK, or NO
|
|
* for that matter.
|
|
*/
|
|
if (!p || strncasecmp (p, " NO", 3) != 0) {
|
|
g_warning ("Unexpected response from IMAP server: %s",
|
|
respbuf);
|
|
camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
|
|
_("Unexpected response from IMAP "
|
|
"server: %s"), respbuf);
|
|
camel_imap_response_free_without_processing (store, response);
|
|
return NULL;
|
|
}
|
|
|
|
p += 3;
|
|
if (!*p++)
|
|
p = NULL;
|
|
camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
|
|
_("IMAP command failed: %s"),
|
|
p ? p : _("Unknown error"));
|
|
camel_imap_response_free_without_processing (store, response);
|
|
return NULL;
|
|
}
|
|
|
|
/* Given a line that is the start of an untagged response, read and
|
|
* return the complete response, which may include an arbitrary number
|
|
* of literals.
|
|
*/
|
|
static char *
|
|
imap_read_untagged (CamelImapStore *store, char *line, CamelException *ex)
|
|
{
|
|
int fulllen, ldigits, nread, i;
|
|
unsigned int length;
|
|
GPtrArray *data;
|
|
GString *str;
|
|
char *end, *p, *s, *d;
|
|
|
|
p = strrchr (line, '{');
|
|
if (!p)
|
|
return line;
|
|
|
|
data = g_ptr_array_new ();
|
|
fulllen = 0;
|
|
|
|
while (1) {
|
|
str = g_string_new (line);
|
|
g_free (line);
|
|
fulllen += str->len;
|
|
g_ptr_array_add (data, str);
|
|
|
|
p = strrchr (str->str, '{');
|
|
if (!p)
|
|
break;
|
|
|
|
length = strtoul (p + 1, &end, 10);
|
|
if (*end != '}' || *(end + 1) || end == p + 1 || length >= UINT_MAX - 2)
|
|
break;
|
|
ldigits = end - (p + 1);
|
|
|
|
/* Read the literal */
|
|
str = g_string_sized_new (length + 2);
|
|
str->str[0] = '\n';
|
|
nread = camel_stream_read (store->istream, str->str + 1, length);
|
|
if (nread == -1) {
|
|
if (errno == EINTR)
|
|
camel_exception_set (ex, CAMEL_EXCEPTION_USER_CANCEL,
|
|
_("Operation cancelled"));
|
|
else
|
|
camel_exception_set (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
|
|
g_strerror (errno));
|
|
camel_service_disconnect (CAMEL_SERVICE (store), FALSE, NULL);
|
|
g_string_free (str, TRUE);
|
|
goto lose;
|
|
}
|
|
if (nread < length) {
|
|
camel_exception_set (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
|
|
_("Server response ended too soon."));
|
|
camel_service_disconnect (CAMEL_SERVICE (store), FALSE, NULL);
|
|
g_string_free (str, TRUE);
|
|
goto lose;
|
|
}
|
|
str->str[length + 1] = '\0';
|
|
|
|
/* Fix up the literal, turning CRLFs into LF. Also, if
|
|
* we find any embedded NULs, strip them. This is
|
|
* dubious, but:
|
|
* - The IMAP grammar says you can't have NULs here
|
|
* anyway, so this will not affect our behavior
|
|
* against any completely correct server.
|
|
* - WU-imapd 12.264 (at least) will cheerily pass
|
|
* NULs along if they are embedded in the message
|
|
*/
|
|
|
|
s = d = str->str + 1;
|
|
end = str->str + 1 + length;
|
|
while (s < end) {
|
|
while (s < end && *s == '\0') {
|
|
s++;
|
|
length--;
|
|
}
|
|
if (*s == '\r' && *(s + 1) == '\n') {
|
|
s++;
|
|
length--;
|
|
}
|
|
*d++ = *s++;
|
|
}
|
|
*d = '\0';
|
|
str->len = length + 1;
|
|
|
|
/* p points to the "{" in the line that starts the
|
|
* literal. The length of the CR-less response must be
|
|
* less than or equal to the length of the response
|
|
* with CRs, therefore overwriting the old value with
|
|
* the new value cannot cause an overrun. However, we
|
|
* don't want it to be shorter either, because then the
|
|
* GString's length would be off...
|
|
*/
|
|
sprintf (p, "{%0*d}", ldigits, length);
|
|
|
|
fulllen += str->len;
|
|
g_ptr_array_add (data, str);
|
|
|
|
/* Read the next line. */
|
|
if (camel_imap_store_readline (store, &line, ex) < 0)
|
|
goto lose;
|
|
}
|
|
|
|
/* Now reassemble the data. */
|
|
p = line = g_malloc (fulllen + 1);
|
|
for (i = 0; i < data->len; i++) {
|
|
str = data->pdata[i];
|
|
memcpy (p, str->str, str->len);
|
|
p += str->len;
|
|
g_string_free (str, TRUE);
|
|
}
|
|
*p = '\0';
|
|
g_ptr_array_free (data, TRUE);
|
|
return line;
|
|
|
|
lose:
|
|
for (i = 0; i < data->len; i++)
|
|
g_string_free (data->pdata[i], TRUE);
|
|
g_ptr_array_free (data, TRUE);
|
|
return NULL;
|
|
}
|
|
|
|
|
|
/**
|
|
* camel_imap_response_free:
|
|
* @store: the CamelImapStore the response is from
|
|
* @response: a CamelImapResponse
|
|
*
|
|
* Frees all of the data in @response and processes any untagged
|
|
* EXPUNGE and EXISTS responses in it. Releases @store's connect_lock.
|
|
**/
|
|
void
|
|
camel_imap_response_free (CamelImapStore *store, CamelImapResponse *response)
|
|
{
|
|
int i, number, exists = 0;
|
|
GArray *expunged = NULL;
|
|
char *resp, *p;
|
|
|
|
if (!response)
|
|
return;
|
|
|
|
for (i = 0; i < response->untagged->len; i++) {
|
|
resp = response->untagged->pdata[i];
|
|
|
|
if (response->folder) {
|
|
/* Check if it's something we need to handle. */
|
|
number = strtoul (resp + 2, &p, 10);
|
|
if (!strcasecmp (p, " EXISTS")) {
|
|
exists = number;
|
|
} else if (!strcasecmp (p, " EXPUNGE")) {
|
|
if (!expunged) {
|
|
expunged = g_array_new (FALSE, FALSE,
|
|
sizeof (int));
|
|
}
|
|
g_array_append_val (expunged, number);
|
|
}
|
|
}
|
|
g_free (resp);
|
|
}
|
|
|
|
g_ptr_array_free (response->untagged, TRUE);
|
|
g_free (response->status);
|
|
|
|
if (response->folder) {
|
|
if (exists > 0 || expunged) {
|
|
/* Update the summary */
|
|
camel_imap_folder_changed (response->folder,
|
|
exists, expunged, NULL);
|
|
if (expunged)
|
|
g_array_free (expunged, TRUE);
|
|
}
|
|
|
|
camel_object_unref (CAMEL_OBJECT (response->folder));
|
|
}
|
|
|
|
g_free (response);
|
|
CAMEL_SERVICE_UNLOCK (store, connect_lock);
|
|
}
|
|
|
|
/**
|
|
* camel_imap_response_free_without_processing:
|
|
* @store: the CamelImapStore the response is from.
|
|
* @response: a CamelImapResponse:
|
|
*
|
|
* Frees all of the data in @response without processing any untagged
|
|
* responses. Releases @store's command lock.
|
|
**/
|
|
void
|
|
camel_imap_response_free_without_processing (CamelImapStore *store,
|
|
CamelImapResponse *response)
|
|
{
|
|
if (!response)
|
|
return;
|
|
|
|
if (response->folder) {
|
|
camel_object_unref (CAMEL_OBJECT (response->folder));
|
|
response->folder = NULL;
|
|
}
|
|
camel_imap_response_free (store, response);
|
|
}
|
|
|
|
/**
|
|
* camel_imap_response_extract:
|
|
* @store: the store the response came from
|
|
* @response: the response data returned from camel_imap_command
|
|
* @type: the response type to extract
|
|
* @ex: a CamelException
|
|
*
|
|
* This checks that @response contains a single untagged response of
|
|
* type @type and returns just that response data. If @response
|
|
* doesn't contain the right information, the function will set @ex
|
|
* and return %NULL. Either way, @response will be freed and the
|
|
* store's connect_lock released.
|
|
*
|
|
* Return value: the desired response string, which the caller must free.
|
|
**/
|
|
char *
|
|
camel_imap_response_extract (CamelImapStore *store,
|
|
CamelImapResponse *response,
|
|
const char *type,
|
|
CamelException *ex)
|
|
{
|
|
int len = strlen (type), i;
|
|
char *resp;
|
|
|
|
len = strlen (type);
|
|
|
|
for (i = 0; i < response->untagged->len; i++) {
|
|
resp = response->untagged->pdata[i];
|
|
/* Skip "* ", and initial sequence number, if present */
|
|
strtoul (resp + 2, &resp, 10);
|
|
if (*resp == ' ')
|
|
resp = (char *) imap_next_word (resp);
|
|
|
|
if (!strncasecmp (resp, type, len))
|
|
break;
|
|
}
|
|
|
|
if (i < response->untagged->len) {
|
|
resp = response->untagged->pdata[i];
|
|
g_ptr_array_remove_index (response->untagged, i);
|
|
} else {
|
|
resp = NULL;
|
|
camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
|
|
_("IMAP server response did not contain "
|
|
"%s information"), type);
|
|
}
|
|
|
|
camel_imap_response_free (store, response);
|
|
return resp;
|
|
}
|
|
|
|
/**
|
|
* camel_imap_response_extract_continuation:
|
|
* @store: the store the response came from
|
|
* @response: the response data returned from camel_imap_command
|
|
* @ex: a CamelException
|
|
*
|
|
* This checks that @response contains a continuation response, and
|
|
* returns just that data. If @response doesn't contain a continuation
|
|
* response, the function will set @ex, release @store's connect_lock,
|
|
* and return %NULL. Either way, @response will be freed.
|
|
*
|
|
* Return value: the desired response string, which the caller must free.
|
|
**/
|
|
char *
|
|
camel_imap_response_extract_continuation (CamelImapStore *store,
|
|
CamelImapResponse *response,
|
|
CamelException *ex)
|
|
{
|
|
char *status;
|
|
|
|
if (response->status && *response->status == '+') {
|
|
status = response->status;
|
|
response->status = NULL;
|
|
camel_imap_response_free (store, response);
|
|
return status;
|
|
}
|
|
|
|
camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
|
|
_("Unexpected OK response from IMAP server: %s"),
|
|
response->status);
|
|
camel_imap_response_free (store, response);
|
|
return NULL;
|
|
}
|
|
|
|
static char *
|
|
imap_command_strdup_vprintf (CamelImapStore *store, const char *fmt,
|
|
va_list ap)
|
|
{
|
|
GPtrArray *args;
|
|
const char *p, *start;
|
|
char *out, *outptr, *string;
|
|
int num, len, i, arglen;
|
|
|
|
args = g_ptr_array_new ();
|
|
|
|
/* Determine the length of the data */
|
|
len = strlen (fmt);
|
|
p = start = fmt;
|
|
while (*p) {
|
|
p = strchr (start, '%');
|
|
if (!p)
|
|
break;
|
|
|
|
switch (*++p) {
|
|
case 'd':
|
|
num = va_arg (ap, int);
|
|
g_ptr_array_add (args, GINT_TO_POINTER (num));
|
|
start = p + 1;
|
|
len += 10;
|
|
break;
|
|
case 's':
|
|
string = va_arg (ap, char *);
|
|
g_ptr_array_add (args, string);
|
|
start = p + 1;
|
|
len += strlen (string);
|
|
break;
|
|
case 'S':
|
|
case 'F':
|
|
string = va_arg (ap, char *);
|
|
if (*p == 'F') {
|
|
/* NB: this is freed during output */
|
|
char *s = camel_imap_store_summary_full_from_path(store->summary, string);
|
|
string = s?s:camel_utf8_utf7(string);
|
|
}
|
|
arglen = strlen (string);
|
|
g_ptr_array_add (args, string);
|
|
if (imap_is_atom (string)) {
|
|
len += arglen;
|
|
} else {
|
|
if (store->capabilities & IMAP_CAPABILITY_LITERALPLUS)
|
|
len += arglen + 15;
|
|
else
|
|
len += arglen * 2;
|
|
}
|
|
start = p + 1;
|
|
break;
|
|
case '%':
|
|
start = p;
|
|
break;
|
|
default:
|
|
g_warning ("camel-imap-command is not printf. I don't "
|
|
"know what '%%%c' means.", *p);
|
|
start = *p ? p + 1 : p;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Now write out the string */
|
|
outptr = out = g_malloc (len + 1);
|
|
p = start = fmt;
|
|
i = 0;
|
|
while (*p) {
|
|
p = strchr (start, '%');
|
|
if (!p) {
|
|
strcpy (outptr, start);
|
|
break;
|
|
} else {
|
|
strncpy (outptr, start, p - start);
|
|
outptr += p - start;
|
|
}
|
|
|
|
switch (*++p) {
|
|
case 'd':
|
|
num = GPOINTER_TO_INT (args->pdata[i++]);
|
|
outptr += sprintf (outptr, "%d", num);
|
|
break;
|
|
|
|
case 's':
|
|
string = args->pdata[i++];
|
|
outptr += sprintf (outptr, "%s", string);
|
|
break;
|
|
case 'S':
|
|
case 'F':
|
|
string = args->pdata[i++];
|
|
if (imap_is_atom (string)) {
|
|
outptr += sprintf (outptr, "%s", string);
|
|
} else {
|
|
if (store->capabilities & IMAP_CAPABILITY_LITERALPLUS) {
|
|
outptr += sprintf (outptr, "{%d+}\r\n%s", strlen (string), string);
|
|
} else {
|
|
char *quoted = imap_quote_string (string);
|
|
|
|
outptr += sprintf (outptr, "%s", quoted);
|
|
g_free (quoted);
|
|
}
|
|
}
|
|
|
|
if (*p == 'F')
|
|
g_free (string);
|
|
break;
|
|
default:
|
|
*outptr++ = '%';
|
|
*outptr++ = *p;
|
|
}
|
|
|
|
start = *p ? p + 1 : p;
|
|
}
|
|
|
|
return out;
|
|
}
|
|
|
|
static char *
|
|
imap_command_strdup_printf (CamelImapStore *store, const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
char *result;
|
|
|
|
va_start (ap, fmt);
|
|
result = imap_command_strdup_vprintf (store, fmt, ap);
|
|
va_end (ap);
|
|
|
|
return result;
|
|
}
|