aboutsummaryrefslogtreecommitdiffstats
path: root/camel/providers/imap/camel-imap-command.c
diff options
context:
space:
mode:
Diffstat (limited to 'camel/providers/imap/camel-imap-command.c')
-rw-r--r--camel/providers/imap/camel-imap-command.c311
1 files changed, 199 insertions, 112 deletions
diff --git a/camel/providers/imap/camel-imap-command.c b/camel/providers/imap/camel-imap-command.c
index db113e0fd7..428ecf565d 100644
--- a/camel/providers/imap/camel-imap-command.c
+++ b/camel/providers/imap/camel-imap-command.c
@@ -40,36 +40,34 @@
#include "camel-imap-private.h"
#include <camel/camel-exception.h>
+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 CamelImapResponse *imap_read_response (CamelImapStore *store,
- 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: Send a command to a IMAP server and get a response
+ * camel_imap_command:
* @store: the IMAP store
* @folder: The folder to perform the operation in (or %NULL if not
* relevant).
* @ex: a CamelException
- * @fmt: an sort of printf-style format string, followed by arguments
+ * @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. It then reads the
- * server's response(s) and parses the final result.
+ * 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.
- *
- * @fmt can include the following %-escapes ONLY:
- * %s, %d, %%: as with printf
- * %S: an IMAP "string" (quoted string or literal)
*
- * %S strings will be passed as literals if the server supports LITERAL+
- * and quoted strings otherwise. (%S does not support strings that
- * contain newlines.)
+ * See camel_imap_command_start() for details on @fmt.
*
* On success, the store's command_lock will be locked. It will be freed
* when you call camel_imap_response_free. (The lock is recursive, so
@@ -84,73 +82,123 @@ CamelImapResponse *
camel_imap_command (CamelImapStore *store, CamelFolder *folder,
CamelException *ex, const char *fmt, ...)
{
- gchar *cmdbuf;
va_list ap;
- CamelException internal_ex;
+ char *cmd;
CAMEL_IMAP_STORE_LOCK (store, command_lock);
- /* Check for current folder */
- if (folder && (!fmt || folder != store->current_folder)) {
- CamelImapResponse *response;
-
+ 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;
}
- response = camel_imap_command (store, NULL, ex, "SELECT %S",
- folder->full_name);
- if (!response) {
- CAMEL_IMAP_STORE_UNLOCK (store, command_lock);
- return NULL;
- }
store->current_folder = folder;
camel_object_ref (CAMEL_OBJECT (folder));
+ cmd = imap_command_strdup_printf (store, "SELECT %S",
+ folder->full_name);
+ }
- camel_imap_folder_selected (folder, response, ex);
- if (!fmt) {
- /* This undoes the level of locking we did,
- * but not the level of locking associated with
- * "response".
- */
- CAMEL_IMAP_STORE_UNLOCK (store, command_lock);
- return response;
- }
-
- /* Contrariwise, this undoes "response"s lock,
- * but not our own.
- */
- camel_imap_response_free (store, response);
+ if (!imap_command_start (store, folder, cmd, ex)) {
+ g_free (cmd);
+ CAMEL_IMAP_STORE_UNLOCK (store, command_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)
+ *
+ * %S strings will be passed as literals if the server supports LITERAL+
+ * and quoted strings otherwise. (%S does not support strings that
+ * contain newlines.)
+ *
+ * On success, the store's command_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;
- /* Send the command */
va_start (ap, fmt);
- cmdbuf = imap_command_strdup_vprintf (store, fmt, ap);
+ cmd = imap_command_strdup_vprintf (store, fmt, ap);
va_end (ap);
- camel_exception_init (&internal_ex);
- camel_remote_store_send_string (CAMEL_REMOTE_STORE (store), &internal_ex,
- "%c%.5d %s\r\n", store->tag_prefix,
- store->command++, cmdbuf);
- g_free (cmdbuf);
- if (camel_exception_is_set (&internal_ex)) {
- camel_exception_xfer (ex, &internal_ex);
+ CAMEL_IMAP_STORE_LOCK (store, command_lock);
+ ok = imap_command_start (store, folder, cmd, ex);
+ g_free (cmd);
+
+ if (!ok)
CAMEL_IMAP_STORE_UNLOCK (store, command_lock);
- return NULL;
+ return ok;
+}
+
+static gboolean
+imap_command_start (CamelImapStore *store, CamelFolder *folder,
+ const char *cmd, CamelException *ex)
+{
+ /* 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 NULL;
+ 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;
+ }
}
- /* Read the response. */
- return imap_read_response (store, ex);
+ /* Send the command */
+ return camel_remote_store_send_string (CAMEL_REMOTE_STORE (store), ex,
+ "%c%.5d %s\r\n",
+ store->tag_prefix,
+ store->command++, cmd) != -1;
}
/**
- * camel_imap_command_continuation: Send more command data to the IMAP server
+ * camel_imap_command_continuation:
* @store: the IMAP store
+ * @cmd: buffer containing the response/request data
* @ex: a CamelException
- * @cmdbuf: buffer containing the response/request data
*
* This method is for sending continuing responses to the IMAP server
- * after camel_imap_command returns a CAMEL_IMAP_PLUS response.
+ * after camel_imap_command() or camel_imap_command_response() returns
+ * a continuation response.
*
* This function assumes you have an exclusive lock on the remote stream.
*
@@ -158,11 +206,11 @@ camel_imap_command (CamelImapStore *store, CamelFolder *folder,
* command_lock will be released.
**/
CamelImapResponse *
-camel_imap_command_continuation (CamelImapStore *store, CamelException *ex,
- const char *cmdbuf)
+camel_imap_command_continuation (CamelImapStore *store, const char *cmd,
+ CamelException *ex)
{
if (camel_remote_store_send_string (CAMEL_REMOTE_STORE (store), ex,
- "%s\r\n", cmdbuf) < 0) {
+ "%s\r\n", cmd) < 0) {
CAMEL_IMAP_STORE_UNLOCK (store, command_lock);
return NULL;
}
@@ -170,54 +218,90 @@ camel_imap_command_continuation (CamelImapStore *store, CamelException *ex,
return imap_read_response (store, ex);
}
-/* Read the response to an IMAP command. */
-static CamelImapResponse *
-imap_read_response (CamelImapStore *store, CamelException *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)
{
- CamelImapResponse *response;
- CamelException internal_ex;
- char *respbuf, *retcode;
+ CamelImapResponseType type;
+ char *respbuf;
- /* Read first line */
if (camel_remote_store_recv_line (CAMEL_REMOTE_STORE (store),
&respbuf, ex) < 0) {
CAMEL_IMAP_STORE_UNLOCK (store, command_lock);
- return NULL;
+ return CAMEL_IMAP_RESPONSE_ERROR;
}
- response = g_new0 (CamelImapResponse, 1);
- if (camel_disco_store_status (CAMEL_DISCO_STORE (store)) != CAMEL_DISCO_STORE_RESYNCING) {
- response->folder = store->current_folder;
- if (response->folder)
- camel_object_ref (CAMEL_OBJECT (response->folder));
- }
- response->untagged = g_ptr_array_new ();
+ switch (*respbuf) {
+ case '*':
+ type = CAMEL_IMAP_RESPONSE_UNTAGGED;
- camel_exception_init (&internal_ex);
-
- /* Check for untagged data */
- while (!strncmp (respbuf, "* ", 2)) {
/* Read the rest of the response if it is multi-line. */
- respbuf = imap_read_untagged (store, respbuf, &internal_ex);
- if (camel_exception_is_set (&internal_ex))
- break;
-
- if (!g_strncasecmp (respbuf, "* BYE", 5)) {
+ respbuf = imap_read_untagged (store, respbuf, ex);
+ if (!respbuf)
+ type = CAMEL_IMAP_RESPONSE_ERROR;
+ else if (!g_strncasecmp (respbuf, "* BYE", 5)) {
/* Connection was lost, no more data to fetch */
store->connected = FALSE;
g_free (respbuf);
- respbuf = NULL;
- break;
+ type = CAMEL_IMAP_RESPONSE_ERROR;
}
+ break;
+ case '+':
+ type = CAMEL_IMAP_RESPONSE_CONTINUATION;
+ break;
+ default:
+ type = CAMEL_IMAP_RESPONSE_TAGGED;
+ break;
+ }
+ *response = respbuf;
- g_ptr_array_add (response->untagged, respbuf);
- if (camel_remote_store_recv_line (
- CAMEL_REMOTE_STORE (store), &respbuf, ex) < 0)
- break;
+ if (type == CAMEL_IMAP_RESPONSE_ERROR ||
+ type == CAMEL_IMAP_RESPONSE_TAGGED)
+ CAMEL_IMAP_STORE_UNLOCK (store, command_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_IMAP_STORE_LOCK (store, command_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));
}
- if (!respbuf || camel_exception_is_set (&internal_ex)) {
- camel_exception_xfer (ex, &internal_ex);
+ 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;
}
@@ -227,14 +311,14 @@ imap_read_response (CamelImapStore *store, CamelException *ex)
/* Check for OK or continuation response. */
if (*respbuf == '+')
return response;
- retcode = imap_next_word (respbuf);
- if (!strncmp (retcode, "OK", 2))
+ p = strchr (respbuf, ' ');
+ if (p && !g_strncasecmp (p, " OK", 3))
return response;
/* We should never get BAD, or anything else but +, OK, or NO
* for that matter.
*/
- if (strncmp (retcode, "NO", 2) != 0) {
+ if (!p || g_strncasecmp (p, " NO", 3) != 0) {
g_warning ("Unexpected response from IMAP server: %s",
respbuf);
camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
@@ -244,10 +328,12 @@ imap_read_response (CamelImapStore *store, CamelException *ex)
return NULL;
}
- retcode = imap_next_word (retcode);
+ p += 3;
+ if (!*p++)
+ p = NULL;
camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
_("IMAP command failed: %s"),
- retcode ? retcode : _("Unknown error"));
+ p ? p : _("Unknown error"));
camel_imap_response_free_without_processing (store, response);
return NULL;
}
@@ -293,9 +379,9 @@ imap_read_untagged (CamelImapStore *store, char *line, CamelException *ex)
str->str + 1, length);
if (nread == -1) {
if (errno == EINTR)
- camel_exception_set (ex, CAMEL_EXCEPTION_USER_CANCEL, _("Operation cancelled"));
+ camel_exception_set(ex, CAMEL_EXCEPTION_USER_CANCEL, _("Operation cancelled"));
else
- camel_exception_set (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE, strerror(errno));
+ camel_exception_set(ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE, strerror(errno));
camel_service_disconnect (CAMEL_SERVICE (store), FALSE, NULL);
goto lose;
}
@@ -409,13 +495,6 @@ camel_imap_response_free (CamelImapStore *store, CamelImapResponse *response)
sizeof (int));
}
g_array_append_val (expunged, number);
-
- /* camel_imap_folder_changed expects
- * "exists" to be the value after all
- * expunges.
- */
- if (exists)
- exists--;
}
}
g_free (resp);
@@ -427,12 +506,8 @@ camel_imap_response_free (CamelImapStore *store, CamelImapResponse *response)
if (response->folder) {
if (exists > 0 || expunged) {
/* Update the summary */
- CamelException ex;
-
- camel_exception_init (&ex);
- camel_imap_folder_changed (response->folder, exists, expunged, &ex);
- camel_exception_clear (&ex);
-
+ camel_imap_folder_changed (response->folder,
+ exists, expunged, NULL);
if (expunged)
g_array_free (expunged, TRUE);
}
@@ -535,7 +610,6 @@ camel_imap_response_extract_continuation (CamelImapStore *store,
if (response->status && *response->status == '+') {
status = response->status;
response->status = NULL;
- CAMEL_IMAP_STORE_LOCK (store, command_lock);
camel_imap_response_free (store, response);
return status;
}
@@ -651,3 +725,16 @@ imap_command_strdup_vprintf (CamelImapStore *store, const char *fmt,
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;
+}