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.c391
1 files changed, 391 insertions, 0 deletions
diff --git a/camel/providers/imap/camel-imap-command.c b/camel/providers/imap/camel-imap-command.c
new file mode 100644
index 0000000000..e06de34db4
--- /dev/null
+++ b/camel/providers/imap/camel-imap-command.c
@@ -0,0 +1,391 @@
+/* -*- 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@helixcode.com>
+ * Jeffrey Stedfast <fejj@helixcode.com>
+ *
+ * Copyright 2000 Helix Code, Inc. (www.helixcode.com)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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 Street #330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+
+#include <config.h>
+
+#include <stdio.h>
+#include <string.h>
+
+#include "camel-imap-command.h"
+#include "camel-imap-utils.h"
+#include "camel-imap-folder.h"
+#include <camel/camel-exception.h>
+
+static char *imap_read_untagged (CamelImapStore *store, char *line,
+ CamelException *ex);
+static CamelImapResponse *imap_read_response (CamelImapStore *store,
+ CamelException *ex);
+
+/**
+ * camel_imap_command: Send a command to a IMAP server and get a response
+ * @store: the IMAP store
+ * @folder: The folder to perform the operation in (or %NULL if not
+ * relevant).
+ * @ex: a CamelException
+ * @fmt: a printf-style format string, followed by arguments
+ *
+ * This camel method sends the IMAP command specified by @fmt and the
+ * following arguments to the IMAP store specified by @store. It then
+ * reads the server's response(s) and parses the final result.
+ *
+ * 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, ...)
+{
+ gchar *cmdbuf;
+ va_list ap;
+
+ /* Check for current folder */
+ if (folder && folder != store->current_folder) {
+ char *folder_path;
+ CamelImapResponse *response;
+
+ folder_path = camel_imap_store_folder_path (store,
+ folder->full_name);
+ response = camel_imap_command (store, NULL, ex,
+ "SELECT %s", folder_path);
+ g_free (folder_path);
+
+ if (!response) {
+ store->current_folder = NULL;
+ return NULL;
+ }
+ camel_imap_response_free (response);
+
+ store->current_folder = folder;
+ }
+
+ /* Send the command */
+ va_start (ap, fmt);
+ cmdbuf = g_strdup_vprintf (fmt, ap);
+ va_end (ap);
+
+ camel_remote_store_send_string (CAMEL_REMOTE_STORE (store), ex,
+ "A%.5d %s\r\n", store->command++,
+ cmdbuf);
+ g_free (cmdbuf);
+ if (camel_exception_is_set (ex))
+ return NULL;
+
+ /* Read the response. */
+ return imap_read_response (store, ex);
+}
+
+/**
+ * camel_imap_command_continuation: Send more command data to the IMAP server
+ * @store: the IMAP store
+ * @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.
+ *
+ * Return value: as for camel_imap_command()
+ **/
+CamelImapResponse *
+camel_imap_command_continuation (CamelImapStore *store, CamelException *ex,
+ const char *cmdbuf)
+{
+ if (camel_remote_store_send_string (CAMEL_REMOTE_STORE (store), ex,
+ "%s\r\n", cmdbuf) < 0)
+ return NULL;
+
+ return imap_read_response (store, ex);
+}
+
+/* Read the response to an IMAP command. */
+static CamelImapResponse *
+imap_read_response (CamelImapStore *store, CamelException *ex)
+{
+ CamelImapResponse *response;
+ int number, recent = 0;
+ GArray *expunged = NULL;
+ char *respbuf, *retcode, *p;
+
+ /* Read first line */
+ if (camel_remote_store_recv_line (CAMEL_REMOTE_STORE (store),
+ &respbuf, ex) < 0)
+ return NULL;
+
+ response = g_new0 (CamelImapResponse, 1);
+ response->untagged = g_ptr_array_new ();
+
+ /* 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, ex);
+ if (camel_exception_is_set (ex))
+ break;
+
+ /* If it starts with a number, we might deal with
+ * it ourselves.
+ */
+ number = strtoul (respbuf + 2, &p, 10);
+ if (p != respbuf + 2) {
+ p = imap_next_word (p);
+ if (!g_strcasecmp (p, "RECENT")) {
+ recent = number;
+ g_free (respbuf);
+ goto next;
+ } else if (!g_strcasecmp (p, "EXPUNGE")) {
+ if (!expunged) {
+ expunged = g_array_new (FALSE, FALSE,
+ sizeof (int));
+ }
+ g_array_append_val (expunged, number);
+ g_free (respbuf);
+ goto next;
+ }
+ }
+
+ g_ptr_array_add (response->untagged, respbuf);
+ next:
+ if (camel_remote_store_recv_line (
+ CAMEL_REMOTE_STORE (store), &respbuf, ex) < 0)
+ break;
+ }
+
+ /* Update the summary */
+ if (store->current_folder && (recent > 0 || expunged)) {
+ camel_imap_folder_changed (store->current_folder, recent,
+ expunged, NULL);
+ }
+ if (expunged)
+ g_array_free (expunged, TRUE);
+
+ if (camel_exception_is_set (ex)) {
+ camel_imap_response_free (response);
+ return NULL;
+ }
+
+ response->status = respbuf;
+
+ /* Check for OK or continuation response. */
+ if (!strncmp (respbuf, "+ ", 2))
+ return response;
+ retcode = imap_next_word (respbuf);
+ if (!strncmp (retcode, "OK", 2))
+ return response;
+
+ /* We should never get BAD, or anything else but +, OK, or NO
+ * for that matter.
+ */
+ if (strncmp (retcode, "NO", 2) != 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 (response);
+ return NULL;
+ }
+
+ retcode = imap_next_word (retcode);
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
+ "IMAP command failed: %s",
+ retcode ? retcode : "Unknown error");
+ camel_imap_response_free (response);
+ return NULL;
+}
+
+/* Given a line that is the start of an untagged response, read and
+ * return the complete response. (This will be a no-op if the line
+ * in question doesn't end with a literal.)
+ *
+ * FIXME: this won't deal with multiple literals in a single response.
+ */
+static char *
+imap_read_untagged (CamelImapStore *store, char *line, CamelException *ex)
+{
+ int fulllen, length, left, i;
+ GPtrArray *data;
+ char *end, *p;
+
+ p = strrchr (line, '{');
+ if (!p)
+ return line;
+
+ length = strtoul (p + 1, &end, 10);
+ if (*end != '}' || *(end + 1) || end == p + 1)
+ return line;
+
+ fulllen = length + strlen (line) + 1;
+
+ /* OK. We have a literal. @length is the length including CRLF
+ * pairs, which camel_remote_store_recv_line won't return.
+ */
+ data = g_ptr_array_new ();
+ g_ptr_array_add (data, line);
+ left = length;
+ while (1) {
+ if (camel_remote_store_recv_line (CAMEL_REMOTE_STORE (store),
+ &line, ex) < 0) {
+ for (i = 0; i < data->len; i++)
+ g_free (data->pdata[i]);
+ g_ptr_array_free (data, TRUE);
+ return NULL;
+ }
+ g_ptr_array_add (data, line);
+
+ if (left <= 0)
+ break;
+
+ left -= strlen (line) + 2;
+
+ /* The output string will have only LF, not CRLF, so
+ * decrement the length by one.
+ */
+ length--;
+ }
+
+ /* 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.
+ */
+ sprintf (p, "{%d}", length);
+
+ /* Now reassemble the data. */
+ p = line = g_malloc (fulllen + 1);
+ for (i = 0; i < data->len; i++) {
+ length = strlen (data->pdata[i]);
+ memcpy (p, data->pdata[i], length);
+ g_free (data->pdata[i]);
+ p += length;
+ *p++ = '\n';
+ }
+ *p = '\0';
+ g_ptr_array_free (data, TRUE);
+ return line;
+}
+
+
+/**
+ * camel_imap_response_free:
+ * response: a CamelImapResponse:
+ *
+ * Frees all of the data in @response.
+ **/
+void
+camel_imap_response_free (CamelImapResponse *response)
+{
+ int i;
+
+ if (!response)
+ return;
+ for (i = 0; i < response->untagged->len; i++)
+ g_free (response->untagged->pdata[i]);
+ g_ptr_array_free (response->untagged, TRUE);
+ g_free (response->status);
+ g_free (response);
+}
+
+/**
+ * camel_imap_response_extract:
+ * @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.
+ *
+ * Return value: the desired response string, which the caller must free.
+ **/
+char *
+camel_imap_response_extract (CamelImapResponse *response, const char *type,
+ CamelException *ex)
+{
+ int len = strlen (type), i;
+ char *resp;
+
+ 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++;
+
+ if (!g_strncasecmp (resp, type, len))
+ break;
+ g_free (resp);
+ }
+
+ if (i < response->untagged->len) {
+ resp = response->untagged->pdata[i];
+ for (i++; i < response->untagged->len; i++)
+ g_free (response->untagged->pdata[i]);
+ } else {
+ resp = NULL;
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
+ "IMAP server response did not contain "
+ "%s information", type);
+ }
+
+ g_ptr_array_free (response->untagged, TRUE);
+ g_free (response->status);
+ g_free (response);
+ return resp;
+}
+
+/**
+ * camel_imap_response_extract_continuation:
+ * @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 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 (CamelImapResponse *response,
+ CamelException *ex)
+{
+ char *status;
+
+ if (response->status && !strncmp (response->status, "+ ", 2)) {
+ status = response->status;
+ response->status = NULL;
+ camel_imap_response_free (response);
+ return status;
+ }
+
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
+ "Unexpected OK response from IMAP server: %s",
+ response->status);
+ camel_imap_response_free (response);
+ return NULL;
+}