diff options
Diffstat (limited to 'camel/providers/imap4/camel-imap-engine.c')
-rw-r--r-- | camel/providers/imap4/camel-imap-engine.c | 1642 |
1 files changed, 1642 insertions, 0 deletions
diff --git a/camel/providers/imap4/camel-imap-engine.c b/camel/providers/imap4/camel-imap-engine.c new file mode 100644 index 0000000000..36d2e42f9e --- /dev/null +++ b/camel/providers/imap4/camel-imap-engine.c @@ -0,0 +1,1642 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* Camel + * Copyright (C) 1999-2004 Jeffrey Stedfast + * + * 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. + */ + + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <string.h> +#include <ctype.h> +#include <errno.h> + +#include <camel/camel-sasl.h> +#include <camel/camel-stream-buffer.h> + +#include "camel-imap-command.h" +#include "camel-imap-stream.h" +#include "camel-imap-folder.h" +#include "camel-imap-utils.h" + +#include "camel-imap-engine.h" + +#define d(x) x + + +static void camel_imap_engine_class_init (CamelIMAPEngineClass *klass); +static void camel_imap_engine_init (CamelIMAPEngine *engine, CamelIMAPEngineClass *klass); +static void camel_imap_engine_finalize (CamelObject *object); + + +static CamelObjectClass *parent_class = NULL; + + +CamelType +camel_imap_engine_get_type (void) +{ + static CamelType type = 0; + + if (!type) { + type = camel_type_register (CAMEL_TYPE_IMAP_ENGINE, + "CamelIMAPEngine", + sizeof (CamelIMAPEngine), + sizeof (CamelIMAPEngineClass), + (CamelObjectClassInitFunc) camel_imap_engine_class_init, + NULL, + (CamelObjectInitFunc) camel_imap_engine_init, + (CamelObjectFinalizeFunc) camel_imap_engine_finalize); + } + + return type; +} + +static void +camel_imap_engine_class_init (CamelIMAPEngineClass *klass) +{ + parent_class = camel_type_get_global_classfuncs (CAMEL_OBJECT_TYPE); + + klass->tagprefix = 'A'; +} + +static void +camel_imap_engine_init (CamelIMAPEngine *engine, CamelIMAPEngineClass *klass) +{ + engine->state = CAMEL_IMAP_ENGINE_DISCONNECTED; + engine->level = CAMEL_IMAP_LEVEL_UNKNOWN; + + engine->session = NULL; + engine->url = NULL; + + engine->istream = NULL; + engine->ostream = NULL; + + engine->authtypes = g_hash_table_new (g_str_hash, g_str_equal); + + engine->capa = 0; + + /* this is the suggested default, impacts the max command line length we'll send */ + engine->maxlentype = CAMEL_IMAP_ENGINE_MAXLEN_LINE; + engine->maxlen = 1000; + + engine->namespaces.personal = NULL; + engine->namespaces.other = NULL; + engine->namespaces.shared = NULL; + + if (klass->tagprefix > 'Z') + klass->tagprefix = 'A'; + + engine->tagprefix = klass->tagprefix++; + engine->tag = 0; + + engine->nextid = 1; + + engine->folder = NULL; + + e_dlist_init (&engine->queue); +} + +static void +imap_namespace_clear (CamelIMAPNamespace **namespace) +{ + CamelIMAPNamespace *node, *next; + + node = *namespace; + while (node != NULL) { + next = node->next; + g_free (node->path); + g_free (node); + node = next; + } + + *namespace = NULL; +} + +static void +camel_imap_engine_finalize (CamelObject *object) +{ + CamelIMAPEngine *engine = (CamelIMAPEngine *) object; + EDListNode *node; + + if (engine->session) + camel_object_unref (engine->session); + + if (engine->istream) + camel_object_unref (engine->istream); + + if (engine->ostream) + camel_object_unref (engine->ostream); + + g_hash_table_foreach (engine->authtypes, (GHFunc) g_free, NULL); + g_hash_table_destroy (engine->authtypes); + + imap_namespace_clear (&engine->namespaces.personal); + imap_namespace_clear (&engine->namespaces.other); + imap_namespace_clear (&engine->namespaces.shared); + + if (engine->folder) + camel_object_unref (engine->folder); + + while ((node = e_dlist_remhead (&engine->queue))) { + node->next = NULL; + node->prev = NULL; + + camel_imap_command_unref ((CamelIMAPCommand *) node); + } +} + + +/** + * camel_imap_engine_new: + * @session: session + * @url: service url + * + * Returns a new imap engine + **/ +CamelIMAPEngine * +camel_imap_engine_new (CamelSession *session, CamelURL *url) +{ + CamelIMAPEngine *engine; + + g_return_val_if_fail (CAMEL_IS_SESSION (session), NULL); + + engine = (CamelIMAPEngine *) camel_object_new (CAMEL_TYPE_IMAP_ENGINE); + camel_object_ref (session); + engine->session = session; + engine->url = url; + + return engine; +} + + +/** + * camel_imap_engine_take_stream: + * @engine: imap engine + * @stream: tcp stream + * @ex: exception + * + * Gives ownership of @stream to @engine and reads the greeting from + * the stream. + * + * Returns 0 on success or -1 on fail. + * + * Note: on error, @stream will be unref'd. + **/ +int +camel_imap_engine_take_stream (CamelIMAPEngine *engine, CamelStream *stream, CamelException *ex) +{ + camel_imap_token_t token; + int code; + + g_return_val_if_fail (CAMEL_IS_IMAP_ENGINE (engine), -1); + g_return_val_if_fail (CAMEL_IS_STREAM (stream), -1); + + if (engine->istream) + camel_object_unref (engine->istream); + + if (engine->ostream) + camel_object_unref (engine->ostream); + + engine->istream = (CamelIMAPStream *) camel_imap_stream_new (stream); + engine->ostream = camel_stream_buffer_new (stream, CAMEL_STREAM_BUFFER_WRITE); + engine->state = CAMEL_IMAP_ENGINE_CONNECTED; + camel_object_unref (stream); + + if (camel_imap_engine_next_token (engine, &token, ex) == -1) + goto exception; + + if (token.token != '*') { + camel_imap_utils_set_unexpected_token_error (ex, engine, &token); + goto exception; + } + + if ((code = camel_imap_engine_handle_untagged_1 (engine, &token, ex)) == -1) { + goto exception; + } else if (code != CAMEL_IMAP_UNTAGGED_OK && code != CAMEL_IMAP_UNTAGGED_PREAUTH) { + /* FIXME: set an error? */ + goto exception; + } + + return 0; + + exception: + + engine->state = CAMEL_IMAP_ENGINE_DISCONNECTED; + + camel_object_unref (engine->istream); + engine->istream = NULL; + camel_object_unref (engine->ostream); + engine->ostream = NULL; + + return -1; +} + + +/** + * camel_imap_engine_capability: + * @engine: IMAP engine + * @ex: exception + * + * Forces the IMAP engine to query the IMAP server for a list of capabilities. + * + * Returns 0 on success or -1 on fail. + **/ +int +camel_imap_engine_capability (CamelIMAPEngine *engine, CamelException *ex) +{ + CamelIMAPCommand *ic; + int id, retval = 0; + + ic = camel_imap_engine_queue (engine, NULL, "CAPABILITY\r\n"); + + while ((id = camel_imap_engine_iterate (engine)) < ic->id && id != -1) + ; + + if (id == -1 || ic->status != CAMEL_IMAP_COMMAND_COMPLETE) { + camel_exception_xfer (ex, &ic->ex); + retval = -1; + } + + camel_imap_command_unref (ic); + + return retval; +} + + +/** + * camel_imap_engine_namespace: + * @engine: IMAP engine + * @ex: exception + * + * Forces the IMAP engine to query the IMAP server for a list of namespaces. + * + * Returns 0 on success or -1 on fail. + **/ +int +camel_imap_engine_namespace (CamelIMAPEngine *engine, CamelException *ex) +{ + camel_imap_list_t *list; + GPtrArray *array = NULL; + CamelIMAPCommand *ic; + int id, i; + + if (engine->capa & CAMEL_IMAP_CAPABILITY_NAMESPACE) { + ic = camel_imap_engine_queue (engine, NULL, "NAMESPACE\r\n"); + } else { + ic = camel_imap_engine_queue (engine, NULL, "LIST \"\" \"\"\r\n"); + camel_imap_command_register_untagged (ic, "LIST", camel_imap_untagged_list); + ic->user_data = array = g_ptr_array_new (); + } + + while ((id = camel_imap_engine_iterate (engine)) < ic->id && id != -1) + ; + + if (id == -1 || ic->status != CAMEL_IMAP_COMMAND_COMPLETE) { + camel_exception_xfer (ex, &ic->ex); + camel_imap_command_unref (ic); + return -1; + } + + if (array != NULL) { + if (ic->result == CAMEL_IMAP_RESULT_OK) { + CamelIMAPNamespace *namespace; + + g_assert (array->len == 1); + list = array->pdata[0]; + + namespace = g_new (CamelIMAPNamespace, 1); + namespace->next = NULL; + namespace->path = g_strdup (""); + namespace->sep = list->delim; + + engine->namespaces.personal = namespace; + } else { + /* should never *ever* happen */ + } + + for (i = 0; i < array->len; i++) { + list = array->pdata[i]; + g_free (list->name); + g_free (list); + } + + g_ptr_array_free (array, TRUE); + } + + camel_imap_command_unref (ic); + + return 0; +} + + +int +camel_imap_engine_select_folder (CamelIMAPEngine *engine, CamelFolder *folder, CamelException *ex) +{ + CamelIMAPRespCode *resp; + CamelIMAPCommand *ic; + int id, retval = 0; + int i; + + g_return_val_if_fail (CAMEL_IS_IMAP_ENGINE (engine), -1); + g_return_val_if_fail (CAMEL_IS_IMAP_FOLDER (folder), -1); + + /* POSSIBLE FIXME: if the folder to be selected will already + * be selected by the time the queue is emptied, simply + * no-op? */ + + ic = camel_imap_engine_queue (engine, folder, "SELECT %F\r\n", folder); + while ((id = camel_imap_engine_iterate (engine)) < ic->id && id != -1) + ; + + if (id == -1 || ic->status != CAMEL_IMAP_COMMAND_COMPLETE) { + camel_exception_xfer (ex, &ic->ex); + camel_imap_command_unref (ic); + return -1; + } + + switch (ic->result) { + case CAMEL_IMAP_RESULT_OK: + /*folder->mode = 0;*/ + for (i = 0; i < ic->resp_codes->len; i++) { + resp = ic->resp_codes->pdata[i]; + switch (resp->code) { + case CAMEL_IMAP_RESP_CODE_PERM_FLAGS: + folder->permanent_flags = resp->v.flags; + break; + case CAMEL_IMAP_RESP_CODE_READONLY: + /*folder->mode = CAMEL_FOLDER_MODE_READ_ONLY;*/ + break; + case CAMEL_IMAP_RESP_CODE_READWRITE: + /*folder->mode = CAMEL_FOLDER_MODE_READ_WRITE;*/ + break; + case CAMEL_IMAP_RESP_CODE_UIDNEXT: + camel_imap_summary_set_uidnext (folder->summary, resp->v.uidnext); + break; + case CAMEL_IMAP_RESP_CODE_UIDVALIDITY: + camel_imap_summary_set_uidvalidity (folder->summary, resp->v.uidvalidity); + break; + case CAMEL_IMAP_RESP_CODE_UNSEEN: + camel_imap_summary_set_unseen (folder->summary, resp->v.unseen); + break; + default: + break; + } + } + + /*if (folder->mode == 0) { + folder->mode = CAMEL_FOLDER_MODE_READ_ONLY; + g_warning ("Expected to find [READ-ONLY] or [READ-WRITE] in SELECT response"); + }*/ + + break; + case CAMEL_IMAP_RESULT_NO: + /* FIXME: would be good to save the NO reason into the err message */ + camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM, + _("Cannot select folder `%s': Invalid mailbox name"), + folder->full_name); + retval = -1; + break; + case CAMEL_IMAP_RESULT_BAD: + camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM, + _("Cannot select folder `%s': Bad command"), + folder->full_name); + retval = -1; + break; + default: + g_assert_not_reached (); + retval = -1; + } + + camel_imap_command_unref (ic); + + return retval; +} + + +static struct { + const char *name; + guint32 flag; +} imap_capabilities[] = { + { "IMAP4", CAMEL_IMAP_CAPABILITY_IMAP4 }, + { "IMAP4REV1", CAMEL_IMAP_CAPABILITY_IMAP4REV1 }, + { "STATUS", CAMEL_IMAP_CAPABILITY_STATUS }, + { "NAMESPACE", CAMEL_IMAP_CAPABILITY_NAMESPACE }, + { "UIDPLUS", CAMEL_IMAP_CAPABILITY_UIDPLUS }, + { "LITERAL+", CAMEL_IMAP_CAPABILITY_LITERALPLUS }, + { "LOGINDISABLED", CAMEL_IMAP_CAPABILITY_LOGINDISABLED }, + { "STARTTLS", CAMEL_IMAP_CAPABILITY_STARTTLS }, + { NULL, 0 } +}; + +static gboolean +auth_free (gpointer key, gpointer value, gpointer user_data) +{ + g_free (key); + return TRUE; +} + +static int +engine_parse_capability (CamelIMAPEngine *engine, int sentinel, CamelException *ex) +{ + camel_imap_token_t token; + int i; + + engine->capa = CAMEL_IMAP_CAPABILITY_utf8_search; + engine->level = 0; + + g_hash_table_foreach_remove (engine->authtypes, (GHRFunc) auth_free, NULL); + + if (camel_imap_engine_next_token (engine, &token, ex) == -1) + return -1; + + while (token.token == CAMEL_IMAP_TOKEN_ATOM) { + if (!strncasecmp ("AUTH=", token.v.atom, 5)) { + CamelServiceAuthType *auth; + + if ((auth = camel_sasl_authtype (token.v.atom + 5)) != NULL) + g_hash_table_insert (engine->authtypes, g_strdup (token.v.atom + 5), auth); + } else { + for (i = 0; imap_capabilities[i].name; i++) { + if (!strcasecmp (imap_capabilities[i].name, token.v.atom)) + engine->capa |= imap_capabilities[i].flag; + } + } + + if (camel_imap_engine_next_token (engine, &token, ex) == -1) + return -1; + } + + if (token.token != sentinel) { + camel_imap_utils_set_unexpected_token_error (ex, engine, &token); + return -1; + } + + /* unget our sentinel token */ + camel_imap_stream_unget_token (engine->istream, &token); + + /* figure out which version of IMAP we are dealing with */ + if (engine->capa & CAMEL_IMAP_CAPABILITY_IMAP4REV1) { + engine->level = CAMEL_IMAP_LEVEL_IMAP4REV1; + engine->capa |= CAMEL_IMAP_CAPABILITY_STATUS; + } else if (engine->capa & CAMEL_IMAP_CAPABILITY_IMAP4) { + engine->level = CAMEL_IMAP_LEVEL_IMAP4; + } else { + engine->level = CAMEL_IMAP_LEVEL_UNKNOWN; + } + + return 0; +} + +static int +engine_parse_flags_list (CamelIMAPEngine *engine, CamelIMAPRespCode *resp, int perm, CamelException *ex) +{ + guint32 flags = 0; + + if (camel_imap_parse_flags_list (engine, &flags, ex) == -1) + return-1; + + if (resp != NULL) + resp->v.flags = flags; + + if (engine->current && engine->current->folder) { + if (perm) + ((CamelFolder *) engine->current->folder)->permanent_flags = flags; + /*else + ((CamelFolder *) engine->current->folder)->folder_flags = flags;*/ + } else if (engine->folder) { + if (perm) + ((CamelFolder *) engine->folder)->permanent_flags = flags; + /*else + ((CamelFolder *) engine->folder)->folder_flags = flags;*/ + } else { + fprintf (stderr, "We seem to be in a bit of a pickle. we've just parsed an untagged %s\n" + "response for a folder, yet we do not currently have a folder selected?\n", + perm ? "PERMANENTFLAGS" : "FLAGS"); + } + + return 0; +} + +static int +engine_parse_flags (CamelIMAPEngine *engine, CamelException *ex) +{ + camel_imap_token_t token; + + if (engine_parse_flags_list (engine, NULL, FALSE, ex) == -1) + return -1; + + if (camel_imap_engine_next_token (engine, &token, ex) == -1) + return -1; + + if (token.token != '\n') { + d(fprintf (stderr, "Expected to find a '\\n' token after the FLAGS response\n")); + camel_imap_utils_set_unexpected_token_error (ex, engine, &token); + return -1; + } + + return 0; +} + + +enum { + IMAP_STATUS_MESSAGES, + IMAP_STATUS_RECENT, + IMAP_STATUS_UIDNEXT, + IMAP_STATUS_UIDVALIDITY, + IMAP_STATUS_UNSEEN, + IMAP_STATUS_UNKNOWN +}; + +static struct { + const char *name; + int type; +} imap_status[] = { + { "MESSAGES", IMAP_STATUS_MESSAGES }, + { "RECENT", IMAP_STATUS_RECENT }, + { "UIDNEXT", IMAP_STATUS_UIDNEXT }, + { "UIDVALIDITY", IMAP_STATUS_UIDVALIDITY }, + { "UNSEEN", IMAP_STATUS_UNSEEN }, + { NULL, IMAP_STATUS_UNKNOWN }, +}; + +static int +engine_parse_status (CamelIMAPEngine *engine, CamelException *ex) +{ + camel_imap_token_t token; + char *mailbox; + int type; + + if (camel_imap_engine_next_token (engine, &token, ex) == -1) + return -1; + + switch (token.token) { + case CAMEL_IMAP_TOKEN_ATOM: + mailbox = g_strdup (token.v.atom); + break; + case CAMEL_IMAP_TOKEN_QSTRING: + mailbox = g_strdup (token.v.qstring); + break; + case CAMEL_IMAP_TOKEN_LITERAL: + /* FIXME: can this happen? if so implement it */ + default: + fprintf (stderr, "Unexpected token in IMAP untagged STATUS response: %s%c\n", + token.token == CAMEL_IMAP_TOKEN_NIL ? "NIL" : "", + (unsigned char) (token.token & 0xff)); + camel_imap_utils_set_unexpected_token_error (ex, engine, &token); + return -1; + } + + if (camel_imap_engine_next_token (engine, &token, ex) == -1) { + g_free (mailbox); + return -1; + } + + if (token.token != '(') { + d(fprintf (stderr, "Expected to find a '(' token after the mailbox token in the STATUS response\n")); + camel_imap_utils_set_unexpected_token_error (ex, engine, &token); + g_free (mailbox); + return -1; + } + + if (camel_imap_engine_next_token (engine, &token, ex) == -1) { + g_free (mailbox); + return -1; + } + + while (token.token == CAMEL_IMAP_TOKEN_ATOM) { + const unsigned char *inptr; + unsigned int v = 0; + + /* parse the status messages list */ + for (type = 0; imap_status[type].name; type++) { + if (!strcasecmp (imap_status[type].name, token.v.atom)) + break; + } + + if (type == IMAP_STATUS_UNKNOWN) + fprintf (stderr, "unrecognized token in STATUS list: %s\n", token.v.atom); + + if (camel_imap_engine_next_token (engine, &token, ex) == -1) { + g_free (mailbox); + return -1; + } + + if (token.token != CAMEL_IMAP_TOKEN_ATOM) + break; + + if (type == IMAP_STATUS_UIDNEXT || type == IMAP_STATUS_UIDVALIDITY) { + /* these tokens should be numeric, but we + * treat them as strings internally so we are + * special-casing them here */ + + /* FIXME: save the UIDNEXT/UIDVALIDITY value */ + } else { + inptr = (const unsigned char *) token.v.atom; + while (*inptr && isdigit ((int) *inptr) && v < (UINT_MAX / 10)) + v = (v * 10) + (*inptr++ - '0'); + + if (*inptr != '\0') { + if (type == IMAP_STATUS_UNKNOWN) { + /* we'll let it slide... unget this token and continue */ + camel_imap_stream_unget_token (engine->istream, &token); + goto loop; + } + + d(fprintf (stderr, "Encountered non-numeric token after %s in untagged STATUS response: %s\n", + imap_status[type].name, token.v.atom)); + goto loop; + } + + switch (type) { + case IMAP_STATUS_MESSAGES: + /* FIXME: save value */ + break; + case IMAP_STATUS_RECENT: + /* FIXME: save value */ + break; + case IMAP_STATUS_UNSEEN: + /* FIXME: save value */ + break; + default: + g_assert_not_reached (); + } + } + + loop: + if (camel_imap_engine_next_token (engine, &token, ex) == -1) { + g_free (mailbox); + return -1; + } + } + + /* don't need this anymore... */ + g_free (mailbox); + + if (token.token != ')') { + d(fprintf (stderr, "Expected to find a ')' token terminating the untagged STATUS response\n")); + camel_imap_utils_set_unexpected_token_error (ex, engine, &token); + return -1; + } + + if (camel_imap_engine_next_token (engine, &token, ex) == -1) + return -1; + + if (token.token != '\n') { + d(fprintf (stderr, "Expected to find a '\\n' token after the STATUS response\n")); + camel_imap_utils_set_unexpected_token_error (ex, engine, &token); + return -1; + } + + return 0; +} + +static int +engine_parse_namespace (CamelIMAPEngine *engine, CamelException *ex) +{ + CamelIMAPNamespace *namespaces[3], *node, *tail; + camel_imap_token_t token; + int i, n = 0; + + imap_namespace_clear (&engine->namespaces.personal); + imap_namespace_clear (&engine->namespaces.other); + imap_namespace_clear (&engine->namespaces.shared); + + if (camel_imap_engine_next_token (engine, &token, ex) == -1) + return -1; + + do { + namespaces[n] = NULL; + tail = (CamelIMAPNamespace *) &namespaces[n]; + + if (token.token == '(') { + /* decode the list of namespace pairs */ + if (camel_imap_engine_next_token (engine, &token, ex) == -1) + goto exception; + + while (token.token == '(') { + /* decode a namespace pair */ + + /* get the path name token */ + if (camel_imap_engine_next_token (engine, &token, ex) == -1) + goto exception; + + if (token.token != CAMEL_IMAP_TOKEN_QSTRING) { + d(fprintf (stderr, "Expected to find a qstring token as first element in NAMESPACE pair\n")); + camel_imap_utils_set_unexpected_token_error (ex, engine, &token); + goto exception; + } + + node = g_new (CamelIMAPNamespace, 1); + node->next = NULL; + node->path = g_strdup (token.v.qstring); + + /* get the path delimiter token */ + if (camel_imap_engine_next_token (engine, &token, ex) == -1) { + g_free (node->path); + g_free (node); + + goto exception; + } + + if (token.token != CAMEL_IMAP_TOKEN_QSTRING || strlen (token.v.qstring) > 1) { + d(fprintf (stderr, "Expected to find a qstring token as second element in NAMESPACE pair\n")); + camel_imap_utils_set_unexpected_token_error (ex, engine, &token); + g_free (node->path); + g_free (node); + + goto exception; + } + + node->sep = *token.v.qstring; + tail->next = node; + tail = node; + + /* canonicalise the namespace path */ + if (node->path[strlen (node->path) - 1] == node->sep) + node->path[strlen (node->path) - 1] = '\0'; + + /* get the closing ')' for this namespace pair */ + if (camel_imap_engine_next_token (engine, &token, ex) == -1) + goto exception; + + if (token.token != ')') { + d(fprintf (stderr, "Expected to find a ')' token to close the current namespace pair\n")); + camel_imap_utils_set_unexpected_token_error (ex, engine, &token); + + goto exception; + } + + /* get the next token (should be either '(' or ')') */ + if (camel_imap_engine_next_token (engine, &token, ex) == -1) + goto exception; + } + + if (token.token != ')') { + d(fprintf (stderr, "Expected to find a ')' to close the current namespace list\n")); + camel_imap_utils_set_unexpected_token_error (ex, engine, &token); + goto exception; + } + } else if (token.token == CAMEL_IMAP_TOKEN_NIL) { + /* namespace list is NIL */ + namespaces[n] = NULL; + } else { + d(fprintf (stderr, "Expected to find either NIL or '(' token in untagged NAMESPACE response\n")); + camel_imap_utils_set_unexpected_token_error (ex, engine, &token); + goto exception; + } + + /* get the next token (should be either '(', NIL, or '\n') */ + if (camel_imap_engine_next_token (engine, &token, ex) == -1) + goto exception; + + n++; + } while (n < 3); + + engine->namespaces.personal = namespaces[0]; + engine->namespaces.other = namespaces[1]; + engine->namespaces.shared = namespaces[2]; + + return 0; + + exception: + + for (i = 0; i <= n; i++) + imap_namespace_clear (&namespaces[i]); + + return -1; +} + + +/** + * + * resp-text-code = "ALERT" / + * "BADCHARSET" [SP "(" astring *(SP astring) ")" ] / + * capability-data / "PARSE" / + * "PERMANENTFLAGS" SP "(" [flag-perm *(SP flag-perm)] ")" / + * "READ-ONLY" / "READ-WRITE" / "TRYCREATE" / + * "UIDNEXT" SP nz-number / "UIDVALIDITY" SP nz-number / + * "UNSEEN" SP nz-number / + * atom [SP 1*<any TEXT-CHAR except "]">] + **/ + +static struct { + const char *name; + camel_imap_resp_code_t code; + int save; +} imap_resp_codes[] = { + { "ALERT", CAMEL_IMAP_RESP_CODE_ALERT, 0 }, + { "BADCHARSET", CAMEL_IMAP_RESP_CODE_BADCHARSET, 0 }, + { "CAPABILITY", CAMEL_IMAP_RESP_CODE_CAPABILITY, 0 }, + { "PARSE", CAMEL_IMAP_RESP_CODE_PARSE, 1 }, + { "PERMANENTFLAGS", CAMEL_IMAP_RESP_CODE_PERM_FLAGS, 1 }, + { "READ-ONLY", CAMEL_IMAP_RESP_CODE_READONLY, 1 }, + { "READ-WRITE", CAMEL_IMAP_RESP_CODE_READWRITE, 1 }, + { "TRYCREATE", CAMEL_IMAP_RESP_CODE_TRYCREATE, 1 }, + { "UIDNEXT", CAMEL_IMAP_RESP_CODE_UIDNEXT, 1 }, + { "UIDVALIDITY", CAMEL_IMAP_RESP_CODE_UIDVALIDITY, 1 }, + { "UNSEEN", CAMEL_IMAP_RESP_CODE_UNSEEN, 1 }, + { "NEWNAME", CAMEL_IMAP_RESP_CODE_NEWNAME, 1 }, + { "APPENDUID", CAMEL_IMAP_RESP_CODE_APPENDUID, 1 }, + { "COPYUID", CAMEL_IMAP_RESP_CODE_COPYUID, 1 }, + { NULL, CAMEL_IMAP_RESP_CODE_UNKNOWN, 0 } +}; + + +int +camel_imap_engine_parse_resp_code (CamelIMAPEngine *engine, CamelException *ex) +{ + CamelIMAPRespCode *resp = NULL; + camel_imap_resp_code_t code; + camel_imap_token_t token; + unsigned char *linebuf; + size_t len; + + if (camel_imap_engine_next_token (engine, &token, ex) == -1) + return -1; + + if (token.token != '[') { + d(fprintf (stderr, "Expected a '[' token (followed by a RESP-CODE)\n")); + camel_imap_utils_set_unexpected_token_error (ex, engine, &token); + return -1; + } + + if (camel_imap_engine_next_token (engine, &token, ex) == -1) + return -1; + + if (token.token != CAMEL_IMAP_TOKEN_ATOM) { + d(fprintf (stderr, "Expected an atom token containing a RESP-CODE\n")); + camel_imap_utils_set_unexpected_token_error (ex, engine, &token); + return -1; + } + + if (token.v.atom[strlen (token.v.atom) - 1] == ']') { + /* split this atom ("FOO]") into 2 tokens: "FOO" and "]" */ + token.token = ']'; + token.v.atom[strlen (token.v.atom) - 1] = '\0'; + camel_imap_stream_unget_token (engine->istream, &token); + token.token = CAMEL_IMAP_TOKEN_ATOM; + } + + for (code = 0; imap_resp_codes[code].name; code++) { + if (!strcmp (imap_resp_codes[code].name, token.v.atom)) { + if (engine->current && imap_resp_codes[code].save) { + resp = g_new0 (CamelIMAPRespCode, 1); + resp->code = code; + } + break; + } + } + + switch (code) { + case CAMEL_IMAP_RESP_CODE_BADCHARSET: + /* apparently we don't support UTF-8 afterall */ + engine->capa &= ~CAMEL_IMAP_CAPABILITY_utf8_search; + break; + case CAMEL_IMAP_RESP_CODE_CAPABILITY: + /* capability list follows */ + if (engine_parse_capability (engine, ']', ex) == -1) + goto exception; + break; + case CAMEL_IMAP_RESP_CODE_PERM_FLAGS: + /* flag list follows */ + if (engine_parse_flags_list (engine, resp, TRUE, ex) == -1) + goto exception; + break; + case CAMEL_IMAP_RESP_CODE_READONLY: + break; + case CAMEL_IMAP_RESP_CODE_READWRITE: + break; + case CAMEL_IMAP_RESP_CODE_TRYCREATE: + break; + case CAMEL_IMAP_RESP_CODE_UIDNEXT: + if (camel_imap_engine_next_token (engine, &token, ex) == -1) + goto exception; + + if (token.token != CAMEL_IMAP_TOKEN_NUMBER) { + d(fprintf (stderr, "Expected an nz_number token as an argument to the UIDNEXT RESP-CODE\n")); + camel_imap_utils_set_unexpected_token_error (ex, engine, &token); + goto exception; + } + + if (resp != NULL) + resp->v.uidnext = token.v.number; + + break; + case CAMEL_IMAP_RESP_CODE_UIDVALIDITY: + if (camel_imap_engine_next_token (engine, &token, ex) == -1) + goto exception; + + if (token.token != CAMEL_IMAP_TOKEN_NUMBER) { + d(fprintf (stderr, "Expected an nz_number token as an argument to the UIDVALIDITY RESP-CODE\n")); + camel_imap_utils_set_unexpected_token_error (ex, engine, &token); + goto exception; + } + + if (resp != NULL) + resp->v.uidvalidity = token.v.number; + + break; + case CAMEL_IMAP_RESP_CODE_UNSEEN: + if (camel_imap_engine_next_token (engine, &token, ex) == -1) + return -1; + + if (token.token != CAMEL_IMAP_TOKEN_NUMBER) { + d(fprintf (stderr, "Expected an nz_number token as an argument to the UNSEEN RESP-CODE\n")); + camel_imap_utils_set_unexpected_token_error (ex, engine, &token); + goto exception; + } + + if (resp != NULL) + resp->v.unseen = token.v.number; + + break; + case CAMEL_IMAP_RESP_CODE_NEWNAME: + /* this RESP-CODE may actually be removed - see here: + * http://www.washington.edu/imap/listarch/2001/msg00058.html */ + + if (camel_imap_engine_next_token (engine, &token, ex) == -1) + return -1; + + if (token.token != CAMEL_IMAP_TOKEN_ATOM && token.token != CAMEL_IMAP_TOKEN_QSTRING) { + d(fprintf (stderr, "Expected an atom or qstring token as the first argument to the NEWNAME RESP-CODE\n")); + camel_imap_utils_set_unexpected_token_error (ex, engine, &token); + goto exception; + } + + if (resp != NULL) + resp->v.newname[0] = g_strdup (token.v.atom); + + if (token.token != CAMEL_IMAP_TOKEN_ATOM && token.token != CAMEL_IMAP_TOKEN_QSTRING) { + d(fprintf (stderr, "Expected an atom or qstring token as the second argument to the NEWNAME RESP-CODE\n")); + camel_imap_utils_set_unexpected_token_error (ex, engine, &token); + goto exception; + } + + if (resp != NULL) + resp->v.newname[1] = g_strdup (token.v.atom); + + break; + case CAMEL_IMAP_RESP_CODE_APPENDUID: + if (camel_imap_engine_next_token (engine, &token, ex) == -1) + return -1; + + if (token.token != CAMEL_IMAP_TOKEN_NUMBER) { + d(fprintf (stderr, "Expected an nz_number token as the first argument to the APPENDUID RESP-CODE\n")); + camel_imap_utils_set_unexpected_token_error (ex, engine, &token); + goto exception; + } + + if (resp != NULL) + resp->v.appenduid.uidvalidity = token.v.number; + + if (camel_imap_engine_next_token (engine, &token, ex) == -1) + return -1; + + if (token.token != CAMEL_IMAP_TOKEN_NUMBER) { + d(fprintf (stderr, "Expected an nz_number token as the second argument to the APPENDUID RESP-CODE\n")); + camel_imap_utils_set_unexpected_token_error (ex, engine, &token); + goto exception; + } + + if (resp != NULL) + resp->v.appenduid.uid = token.v.number; + + break; + case CAMEL_IMAP_RESP_CODE_COPYUID: + if (camel_imap_engine_next_token (engine, &token, ex) == -1) + return -1; + + if (token.token != CAMEL_IMAP_TOKEN_NUMBER) { + d(fprintf (stderr, "Expected an nz_number token as the first argument to the COPYUID RESP-CODE\n")); + camel_imap_utils_set_unexpected_token_error (ex, engine, &token); + goto exception; + } + + if (resp != NULL) + resp->v.copyuid.uidvalidity = token.v.number; + + if (camel_imap_engine_next_token (engine, &token, ex) == -1) + return -1; + + if (token.token != CAMEL_IMAP_TOKEN_ATOM) { + d(fprintf (stderr, "Expected an atom token as the second argument to the COPYUID RESP-CODE\n")); + camel_imap_utils_set_unexpected_token_error (ex, engine, &token); + goto exception; + } + + if (resp != NULL) + resp->v.copyuid.srcset = g_strdup (token.v.atom); + + if (camel_imap_engine_next_token (engine, &token, ex) == -1) + return -1; + + if (token.token != CAMEL_IMAP_TOKEN_ATOM) { + d(fprintf (stderr, "Expected an atom token as the third argument to the APPENDUID RESP-CODE\n")); + camel_imap_utils_set_unexpected_token_error (ex, engine, &token); + goto exception; + } + + if (token.v.atom[strlen (token.v.atom) - 1] == ']') { + /* this should be the case, but if not - no big. */ + token.token = ']'; + token.v.atom[strlen (token.v.atom) - 1] = '\0'; + camel_imap_stream_unget_token (engine->istream, &token); + token.token = CAMEL_IMAP_TOKEN_ATOM; + } + + if (resp != NULL) + resp->v.copyuid.destset = g_strdup (token.v.atom); + + break; + default: + d(fprintf (stderr, "Unknown RESP-CODE encountered: %s\n", token.v.atom)); + + /* extensions are of the form: "[" atom [SPACE 1*<any TEXT_CHAR except "]">] "]" */ + + /* eat up the TEXT_CHARs, being careful to check atoms for a trailing ']' */ + while (token.token != ']' && token.token != '\n') { + if (camel_imap_engine_next_token (engine, &token, ex) == -1) + goto exception; + + if (token.token == CAMEL_IMAP_TOKEN_ATOM) { + if (token.v.atom[strlen (token.v.atom) - 1] == ']') { + token.token = ']'; + break; + } + } + } + + break; + } + + while (token.token != ']' && token.token != '\n') { + if (camel_imap_engine_next_token (engine, &token, ex) == -1) + goto exception; + } + + if (token.token != ']') { + camel_imap_utils_set_unexpected_token_error (ex, engine, &token); + d(fprintf (stderr, "Expected to find a ']' token after the RESP-CODE\n")); + return -1; + } + + if (code == CAMEL_IMAP_RESP_CODE_ALERT) { + if (camel_imap_engine_line (engine, &linebuf, &len, ex) == -1) + goto exception; + + camel_session_alert_user (engine->session, CAMEL_SESSION_ALERT_INFO, linebuf, FALSE); + g_free (linebuf); + } else if (resp != NULL && code == CAMEL_IMAP_RESP_CODE_PARSE) { + if (camel_imap_engine_line (engine, &linebuf, &len, ex) == -1) + goto exception; + + resp->v.parse = linebuf; + } else { + /* eat up the rest of the response */ + if (camel_imap_engine_line (engine, NULL, NULL, ex) == -1) + goto exception; + } + + if (resp != NULL) + g_ptr_array_add (engine->current->resp_codes, resp); + + return 0; + + exception: + + if (resp != NULL) + camel_imap_resp_code_free (resp); + + return -1; +} + + + +/* returns -1 on error, or one of CAMEL_IMAP_UNTAGGED_[OK,NO,BAD,PREAUTH,HANDLED] on success */ +int +camel_imap_engine_handle_untagged_1 (CamelIMAPEngine *engine, camel_imap_token_t *token, CamelException *ex) +{ + int code = CAMEL_IMAP_UNTAGGED_HANDLED; + CamelIMAPCommand *ic = engine->current; + CamelIMAPUntaggedCallback untagged; + CamelFolder *folder; + unsigned int v; + + if (camel_imap_engine_next_token (engine, token, ex) == -1) + return -1; + + if (token->token == CAMEL_IMAP_TOKEN_ATOM) { + if (!strcmp ("BYE", token->v.atom)) { + /* we don't care if we fail here, either way we've been disconnected */ + camel_imap_engine_parse_resp_code (engine, NULL); + engine->state = CAMEL_IMAP_ENGINE_DISCONNECTED; + + /* FIXME: emit a "disconnected" signal for our Store? + * The Store could then initiate a reconnect if + * desirable. */ + + camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM, + _("IMAP server %s unexpectedly disconnected: %s"), + engine->url->host, _("Got BYE response")); + + return -1; + } else if (!strcmp ("CAPABILITY", token->v.atom)) { + /* capability tokens follow */ + if (engine_parse_capability (engine, '\n', ex) == -1) + return -1; + + /* find the eoln token */ + if (camel_imap_engine_next_token (engine, token, ex) == -1) + return -1; + + if (token->token != '\n') { + camel_imap_utils_set_unexpected_token_error (ex, engine, token); + return -1; + } + } else if (!strcmp ("FLAGS", token->v.atom)) { + /* flags list follows */ + if (engine_parse_flags (engine, ex) == -1) + return -1; + } else if (!strcmp ("NAMESPACE", token->v.atom)) { + if (engine_parse_namespace (engine, ex) == -1) + return -1; + } else if (!strcmp ("NO", token->v.atom) || !strcmp ("BAD", token->v.atom)) { + code = !strcmp ("NO", token->v.atom) ? CAMEL_IMAP_UNTAGGED_NO : CAMEL_IMAP_UNTAGGED_BAD; + + /* our command has been rejected */ + if (camel_imap_engine_next_token (engine, token, ex) == -1) + return -1; + + if (token->token == '[') { + /* we have a resp code */ + camel_imap_stream_unget_token (engine->istream, token); + if (camel_imap_engine_parse_resp_code (engine, ex) == -1) + return -1; + } else if (token->token != '\n') { + /* we just have resp text */ + if (camel_imap_engine_line (engine, NULL, NULL, ex) == -1) + return -1; + } + } else if (!strcmp ("OK", token->v.atom)) { + code = CAMEL_IMAP_UNTAGGED_OK; + + if (engine->state == CAMEL_IMAP_ENGINE_CONNECTED) { + /* initial server greeting */ + engine->state = CAMEL_IMAP_ENGINE_PREAUTH; + } + + if (camel_imap_engine_next_token (engine, token, ex) == -1) + return -1; + + if (token->token == '[') { + /* we have a resp code */ + camel_imap_stream_unget_token (engine->istream, token); + if (camel_imap_engine_parse_resp_code (engine, ex) == -1) + return -1; + } else { + /* we just have resp text */ + if (camel_imap_engine_line (engine, NULL, NULL, ex) == -1) + return -1; + } + } else if (!strcmp ("PREAUTH", token->v.atom)) { + code = CAMEL_IMAP_UNTAGGED_PREAUTH; + + if (engine->state == CAMEL_IMAP_ENGINE_CONNECTED) + engine->state = CAMEL_IMAP_ENGINE_AUTHENTICATED; + + if (camel_imap_engine_parse_resp_code (engine, ex) == -1) + return -1; + } else if (!strcmp ("STATUS", token->v.atom)) { + /* FIXME: This should probably be removed... leave it + * up to the caller that sent the STATUS command to + * register an untagged response handler for this */ + + /* next token must be the mailbox name followed by a paren list */ + if (engine_parse_status (engine, ex) == -1) + return -1; + } else if (ic && (untagged = g_hash_table_lookup (ic->untagged, token->v.atom))) { + /* registered untagged handler for imap command */ + if (untagged (engine, ic, 0, token, ex) == -1) + return -1; + } else { + d(fprintf (stderr, "Unhandled atom token in untagged response: %s", token->v.atom)); + + if (camel_imap_engine_eat_line (engine, ex) == -1) + return -1; + } + } else if (token->token == CAMEL_IMAP_TOKEN_NUMBER) { + /* we probably have something like "* 1 EXISTS" */ + v = token->v.number; + + if (camel_imap_engine_next_token (engine, token, ex) == -1) + return -1; + + if (token->token != CAMEL_IMAP_TOKEN_ATOM) { + camel_imap_utils_set_unexpected_token_error (ex, engine, token); + + return -1; + } + + /* which folder is this EXISTS/EXPUNGE/RECENT acting on? */ + if (engine->current && engine->current->folder) + folder = (CamelFolder *) engine->current->folder; + else if (engine->folder) + folder = (CamelFolder *) engine->folder; + else + folder = NULL; + + /* NOTE: these can be over-ridden by a registered untagged response handler */ + if (!strcmp ("EXISTS", token->v.atom)) { + camel_imap_summary_set_exists (folder->summary, v); + } else if (!strcmp ("EXPUNGE", token->v.atom)) { + camel_imap_summary_expunge (folder->summary, (int) v); + } else if (!strcmp ("RECENT", token->v.atom)) { + camel_imap_summary_set_recent (folder->summary, v); + } else if (ic && (untagged = g_hash_table_lookup (ic->untagged, token->v.atom))) { + /* registered untagged handler for imap command */ + if (untagged (engine, ic, v, token, ex) == -1) + return -1; + } else { + d(fprintf (stderr, "Unrecognized untagged response: * %u %s\n", v, token->v.atom)); + } + + /* find the eoln token */ + if (camel_imap_engine_eat_line (engine, ex) == -1) + return -1; + } else { + camel_imap_utils_set_unexpected_token_error (ex, engine, token); + + return -1; + } + + return code; +} + + +void +camel_imap_engine_handle_untagged (CamelIMAPEngine *engine, CamelException *ex) +{ + camel_imap_token_t token; + + g_return_if_fail (CAMEL_IS_IMAP_ENGINE (engine)); + + do { + if (camel_imap_engine_next_token (engine, &token, ex) == -1) + goto exception; + + if (token.token != '*') + break; + + if (camel_imap_engine_handle_untagged_1 (engine, &token, ex) == -1) + goto exception; + } while (1); + + camel_imap_stream_unget_token (engine->istream, &token); + + return; + + exception: + + engine->state = CAMEL_IMAP_ENGINE_DISCONNECTED; +} + + +static int +imap_process_command (CamelIMAPEngine *engine, CamelIMAPCommand *ic) +{ + int retval; + + while ((retval = camel_imap_command_step (ic)) == 0) + ; + + if (retval == -1) { + engine->state = CAMEL_IMAP_ENGINE_DISCONNECTED; + return -1; + } + + return 0; +} + + +static void +engine_prequeue_folder_select (CamelIMAPEngine *engine) +{ + CamelIMAPCommand *ic; + const char *cmd; + + ic = (CamelIMAPCommand *) engine->queue.head; + cmd = (const char *) ic->parts->buffer; + + if (!ic->folder || ic->folder == engine->folder || + !strncmp (cmd, "SELECT ", 7) || !strncmp (cmd, "EXAMINE ", 8)) { + /* no need to pre-queue a SELECT */ + return; + } + + /* we need to pre-queue a SELECT */ + ic = camel_imap_command_new (engine, ic->folder, "SELECT %F\r\n", ic->folder); + camel_imap_engine_prequeue (engine, ic); + ic->user_data = engine; + + camel_imap_command_unref (ic); +} + + +static int +engine_state_change (CamelIMAPEngine *engine, CamelIMAPCommand *ic) +{ + const char *cmd; + int retval = 0; + + cmd = ic->parts->buffer; + if (!strncmp (cmd, "SELECT ", 7) || !strncmp (cmd, "EXAMINE ", 8)) { + if (ic->result == CAMEL_IMAP_RESULT_OK) { + /* Update the selected folder */ + camel_object_ref (ic->folder); + if (engine->folder) + camel_object_unref (engine->folder); + engine->folder = ic->folder; + + engine->state = CAMEL_IMAP_ENGINE_SELECTED; + } else if (ic->user_data == engine) { + /* the engine pre-queued this SELECT command */ + retval = -1; + } + } else if (!strncmp (cmd, "CLOSE", 5)) { + if (ic->result == CAMEL_IMAP_RESULT_OK) + engine->state = CAMEL_IMAP_ENGINE_AUTHENTICATED; + } else if (!strncmp (cmd, "LOGOUT", 6)) { + engine->state = CAMEL_IMAP_ENGINE_DISCONNECTED; + } + + return retval; +} + +/** + * camel_imap_engine_iterate: + * @engine: IMAP engine + * + * Processes the first command in the queue. + * + * Returns the id of the processed command, 0 if there were no + * commands to process, or -1 on error. + * + * Note: more details on the error will be held on the + * CamelIMAPCommand that failed. + **/ +int +camel_imap_engine_iterate (CamelIMAPEngine *engine) +{ + CamelIMAPCommand *ic, *nic; + GPtrArray *resp_codes; + int retval = -1; + + if (e_dlist_empty (&engine->queue)) + return 0; + + /* check to see if we need to pre-queue a SELECT, if so do it */ + engine_prequeue_folder_select (engine); + + engine->current = ic = (CamelIMAPCommand *) e_dlist_remhead (&engine->queue); + ic->status = CAMEL_IMAP_COMMAND_ACTIVE; + + if (imap_process_command (engine, ic) != -1) { + if (engine_state_change (engine, ic) == -1) { + /* This can ONLY happen if @ic was the pre-queued SELECT command + * and it got a NO or BAD response. + * + * We have to pop the next imap command or we'll get into an + * infinite loop. In order to provide @nic's owner with as much + * information as possible, we move all @ic status information + * over to @nic and pretend we just processed @nic. + **/ + + nic = (CamelIMAPCommand *) e_dlist_remhead (&engine->queue); + + nic->status = ic->status; + nic->result = ic->result; + resp_codes = nic->resp_codes; + nic->resp_codes = ic->resp_codes; + ic->resp_codes = resp_codes; + + camel_exception_xfer (&nic->ex, &ic->ex); + + camel_imap_command_unref (ic); + ic = nic; + } + + retval = ic->id; + } + + camel_imap_command_unref (ic); + + return retval; +} + + +/** + * camel_imap_engine_queue: + * @engine: IMAP engine + * @folder: IMAP folder that the command will affect (or %NULL if it doesn't matter) + * @format: command format + * @Varargs: arguments + * + * Basically the same as #camel_imap_command_new() except that this + * function also places the command in the engine queue. + * + * Returns the CamelIMAPCommand. + **/ +CamelIMAPCommand * +camel_imap_engine_queue (CamelIMAPEngine *engine, CamelFolder *folder, const char *format, ...) +{ + CamelIMAPCommand *ic; + va_list args; + + va_start (args, format); + ic = camel_imap_command_newv (engine, (CamelIMAPFolder *) folder, format, args); + va_end (args); + + ic->id = engine->nextid++; + e_dlist_addtail (&engine->queue, (EDListNode *) ic); + camel_imap_command_ref (ic); + + return ic; +} + + +/** + * camel_imap_engine_prequeue: + * @engine: IMAP engine + * @ic: IMAP command to pre-queue + * + * Places @ic at the head of the queue of pending IMAP commands. + **/ +void +camel_imap_engine_prequeue (CamelIMAPEngine *engine, CamelIMAPCommand *ic) +{ + g_return_if_fail (CAMEL_IS_IMAP_ENGINE (engine)); + g_return_if_fail (ic != NULL); + + camel_imap_command_ref (ic); + + if (e_dlist_empty (&engine->queue)) { + e_dlist_addtail (&engine->queue, (EDListNode *) ic); + ic->id = engine->nextid++; + } else { + CamelIMAPCommand *nic; + EDListNode *node; + + node = (EDListNode *) ic; + e_dlist_addhead (&engine->queue, node); + nic = (CamelIMAPCommand *) node->next; + ic->id = nic->id - 1; + + if (ic->id == 0) { + /* increment all command ids */ + node = engine->queue.head; + while (node->next) { + nic = (CamelIMAPCommand *) node; + node = node->next; + nic->id++; + } + } + } +} + + +void +camel_imap_engine_dequeue (CamelIMAPEngine *engine, CamelIMAPCommand *ic) +{ + EDListNode *node = (EDListNode *) ic; + + if (node->next == NULL && node->prev == NULL) + return; + + e_dlist_remove (node); + node->next = NULL; + node->prev = NULL; + + camel_imap_command_unref (ic); +} + + +int +camel_imap_engine_next_token (CamelIMAPEngine *engine, camel_imap_token_t *token, CamelException *ex) +{ + if (camel_imap_stream_next_token (engine->istream, token) == -1) { + camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM, + _("IMAP server %s unexpectedly disconnected: %s"), + engine->url->host, errno ? g_strerror (errno) : _("Unknown")); + return -1; + } + + return 0; +} + + +int +camel_imap_engine_eat_line (CamelIMAPEngine *engine, CamelException *ex) +{ + camel_imap_token_t token; + unsigned char *literal; + int retval; + size_t n; + + do { + if (camel_imap_engine_next_token (engine, &token, ex) == -1) + return -1; + + if (token.token == CAMEL_IMAP_TOKEN_LITERAL) { + while ((retval = camel_imap_stream_literal (engine->istream, &literal, &n)) == 1) + ; + + if (retval == -1) { + camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM, + _("IMAP server %s unexpectedly disconnected: %s"), + engine->url->host, errno ? g_strerror (errno) : _("Unknown")); + + return -1; + } + } + } while (token.token != '\n'); + + return 0; +} + + +int +camel_imap_engine_line (CamelIMAPEngine *engine, unsigned char **line, size_t *len, CamelException *ex) +{ + GByteArray *linebuf; + unsigned char *buf; + size_t buflen; + int retval; + + if (line != NULL) + linebuf = g_byte_array_new (); + + while ((retval = camel_imap_stream_line (engine->istream, &buf, &buflen)) > 0) { + if (line != NULL) + g_byte_array_append (linebuf, buf, buflen); + } + + if (retval == -1) { + camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM, + _("IMAP server %s unexpectedly disconnected: %s"), + engine->url->host, errno ? g_strerror (errno) : _("Unknown")); + + if (line != NULL) + g_byte_array_free (linebuf, TRUE); + + return -1; + } + + if (line != NULL) { + g_byte_array_append (linebuf, buf, buflen); + + *line = linebuf->data; + *len = linebuf->len; + + g_byte_array_free (linebuf, FALSE); + } + + return 0; +} + + +void +camel_imap_resp_code_free (CamelIMAPRespCode *rcode) +{ + switch (rcode->code) { + case CAMEL_IMAP_RESP_CODE_PARSE: + g_free (rcode->v.parse); + break; + case CAMEL_IMAP_RESP_CODE_NEWNAME: + g_free (rcode->v.newname[0]); + g_free (rcode->v.newname[1]); + break; + case CAMEL_IMAP_RESP_CODE_COPYUID: + g_free (rcode->v.copyuid.srcset); + g_free (rcode->v.copyuid.destset); + break; + default: + break; + } + + g_free (rcode); +} |