From 71c5ee30a81ea870329f1c2573cdf6f22cfc7d75 Mon Sep 17 00:00:00 2001 From: Jeffrey Stedfast Date: Tue, 30 Mar 2004 04:24:02 +0000 Subject: Implemented. 2004-03-29 Jeffrey Stedfast * providers/imap4/camel-imap4-folder.c (camel_imap4_folder_new): Implemented. * providers/imap4/camel-imap4-engine.c (engine_parse_namespace): If the namespace begins with "INBOX", canonicalise the INBOX portion (ie, make it all caps). * providers/imap4/camel-imap4-store.c (imap4_noop): Implemented. svn path=/trunk/; revision=25237 --- camel/providers/imap4/camel-imap4-engine.c | 1669 ++++++++++++++++++++++++++++ 1 file changed, 1669 insertions(+) create mode 100644 camel/providers/imap4/camel-imap4-engine.c (limited to 'camel/providers/imap4/camel-imap4-engine.c') diff --git a/camel/providers/imap4/camel-imap4-engine.c b/camel/providers/imap4/camel-imap4-engine.c new file mode 100644 index 0000000000..5e3d93ddaf --- /dev/null +++ b/camel/providers/imap4/camel-imap4-engine.c @@ -0,0 +1,1669 @@ +/* -*- 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 +#endif + +#include +#include +#include + +#include +#include + +#include "camel-imap4-summary.h" +#include "camel-imap4-command.h" +#include "camel-imap4-stream.h" +#include "camel-imap4-folder.h" +#include "camel-imap4-utils.h" + +#include "camel-imap4-engine.h" + +#define d(x) x + + +static void camel_imap4_engine_class_init (CamelIMAP4EngineClass *klass); +static void camel_imap4_engine_init (CamelIMAP4Engine *engine, CamelIMAP4EngineClass *klass); +static void camel_imap4_engine_finalize (CamelObject *object); + + +static CamelObjectClass *parent_class = NULL; + + +CamelType +camel_imap4_engine_get_type (void) +{ + static CamelType type = 0; + + if (!type) { + type = camel_type_register (CAMEL_TYPE_IMAP4_ENGINE, + "CamelIMAP4Engine", + sizeof (CamelIMAP4Engine), + sizeof (CamelIMAP4EngineClass), + (CamelObjectClassInitFunc) camel_imap4_engine_class_init, + NULL, + (CamelObjectInitFunc) camel_imap4_engine_init, + (CamelObjectFinalizeFunc) camel_imap4_engine_finalize); + } + + return type; +} + +static void +camel_imap4_engine_class_init (CamelIMAP4EngineClass *klass) +{ + parent_class = camel_type_get_global_classfuncs (CAMEL_OBJECT_TYPE); + + klass->tagprefix = 'A'; +} + +static void +camel_imap4_engine_init (CamelIMAP4Engine *engine, CamelIMAP4EngineClass *klass) +{ + engine->state = CAMEL_IMAP4_ENGINE_DISCONNECTED; + engine->level = CAMEL_IMAP4_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_IMAP4_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 +imap4_namespace_clear (CamelIMAP4Namespace **namespace) +{ + CamelIMAP4Namespace *node, *next; + + node = *namespace; + while (node != NULL) { + next = node->next; + g_free (node->path); + g_free (node); + node = next; + } + + *namespace = NULL; +} + +static void +camel_imap4_engine_finalize (CamelObject *object) +{ + CamelIMAP4Engine *engine = (CamelIMAP4Engine *) 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); + + imap4_namespace_clear (&engine->namespaces.personal); + imap4_namespace_clear (&engine->namespaces.other); + imap4_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_imap4_command_unref ((CamelIMAP4Command *) node); + } +} + + +/** + * camel_imap4_engine_new: + * @session: session + * @url: service url + * + * Returns a new imap4 engine + **/ +CamelIMAP4Engine * +camel_imap4_engine_new (CamelSession *session, CamelURL *url) +{ + CamelIMAP4Engine *engine; + + g_return_val_if_fail (CAMEL_IS_SESSION (session), NULL); + + engine = (CamelIMAP4Engine *) camel_object_new (CAMEL_TYPE_IMAP4_ENGINE); + camel_object_ref (session); + engine->session = session; + engine->url = url; + + return engine; +} + + +/** + * camel_imap4_engine_take_stream: + * @engine: imap4 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_imap4_engine_take_stream (CamelIMAP4Engine *engine, CamelStream *stream, CamelException *ex) +{ + camel_imap4_token_t token; + int code; + + g_return_val_if_fail (CAMEL_IS_IMAP4_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 = (CamelIMAP4Stream *) camel_imap4_stream_new (stream); + engine->ostream = camel_stream_buffer_new (stream, CAMEL_STREAM_BUFFER_WRITE); + engine->state = CAMEL_IMAP4_ENGINE_CONNECTED; + camel_object_unref (stream); + + if (camel_imap4_engine_next_token (engine, &token, ex) == -1) + goto exception; + + if (token.token != '*') { + camel_imap4_utils_set_unexpected_token_error (ex, engine, &token); + goto exception; + } + + if ((code = camel_imap4_engine_handle_untagged_1 (engine, &token, ex)) == -1) { + goto exception; + } else if (code != CAMEL_IMAP4_UNTAGGED_OK && code != CAMEL_IMAP4_UNTAGGED_PREAUTH) { + /* FIXME: set an error? */ + goto exception; + } + + return 0; + + exception: + + engine->state = CAMEL_IMAP4_ENGINE_DISCONNECTED; + + camel_object_unref (engine->istream); + engine->istream = NULL; + camel_object_unref (engine->ostream); + engine->ostream = NULL; + + return -1; +} + + +/** + * camel_imap4_engine_capability: + * @engine: IMAP4 engine + * @ex: exception + * + * Forces the IMAP4 engine to query the IMAP4 server for a list of capabilities. + * + * Returns 0 on success or -1 on fail. + **/ +int +camel_imap4_engine_capability (CamelIMAP4Engine *engine, CamelException *ex) +{ + CamelIMAP4Command *ic; + int id, retval = 0; + + ic = camel_imap4_engine_queue (engine, NULL, "CAPABILITY\r\n"); + + while ((id = camel_imap4_engine_iterate (engine)) < ic->id && id != -1) + ; + + if (id == -1 || ic->status != CAMEL_IMAP4_COMMAND_COMPLETE) { + camel_exception_xfer (ex, &ic->ex); + retval = -1; + } + + camel_imap4_command_unref (ic); + + return retval; +} + + +/** + * camel_imap4_engine_namespace: + * @engine: IMAP4 engine + * @ex: exception + * + * Forces the IMAP4 engine to query the IMAP4 server for a list of namespaces. + * + * Returns 0 on success or -1 on fail. + **/ +int +camel_imap4_engine_namespace (CamelIMAP4Engine *engine, CamelException *ex) +{ + camel_imap4_list_t *list; + GPtrArray *array = NULL; + CamelIMAP4Command *ic; + int id, i; + + if (engine->capa & CAMEL_IMAP4_CAPABILITY_NAMESPACE) { + ic = camel_imap4_engine_queue (engine, NULL, "NAMESPACE\r\n"); + } else { + ic = camel_imap4_engine_queue (engine, NULL, "LIST \"\" \"\"\r\n"); + camel_imap4_command_register_untagged (ic, "LIST", camel_imap4_untagged_list); + ic->user_data = array = g_ptr_array_new (); + } + + while ((id = camel_imap4_engine_iterate (engine)) < ic->id && id != -1) + ; + + if (id == -1 || ic->status != CAMEL_IMAP4_COMMAND_COMPLETE) { + camel_exception_xfer (ex, &ic->ex); + camel_imap4_command_unref (ic); + return -1; + } + + if (array != NULL) { + if (ic->result == CAMEL_IMAP4_RESULT_OK) { + CamelIMAP4Namespace *namespace; + + g_assert (array->len == 1); + list = array->pdata[0]; + + namespace = g_new (CamelIMAP4Namespace, 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_imap4_command_unref (ic); + + return 0; +} + + +int +camel_imap4_engine_select_folder (CamelIMAP4Engine *engine, CamelFolder *folder, CamelException *ex) +{ + CamelIMAP4RespCode *resp; + CamelIMAP4Command *ic; + int id, retval = 0; + int i; + + g_return_val_if_fail (CAMEL_IS_IMAP4_ENGINE (engine), -1); + g_return_val_if_fail (CAMEL_IS_IMAP4_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_imap4_engine_queue (engine, folder, "SELECT %F\r\n", folder); + while ((id = camel_imap4_engine_iterate (engine)) < ic->id && id != -1) + ; + + if (id == -1 || ic->status != CAMEL_IMAP4_COMMAND_COMPLETE) { + camel_exception_xfer (ex, &ic->ex); + camel_imap4_command_unref (ic); + return -1; + } + + switch (ic->result) { + case CAMEL_IMAP4_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_IMAP4_RESP_CODE_PERM_FLAGS: + folder->permanent_flags = resp->v.flags; + break; + case CAMEL_IMAP4_RESP_CODE_READONLY: + /*folder->mode = CAMEL_FOLDER_MODE_READ_ONLY;*/ + break; + case CAMEL_IMAP4_RESP_CODE_READWRITE: + /*folder->mode = CAMEL_FOLDER_MODE_READ_WRITE;*/ + break; + case CAMEL_IMAP4_RESP_CODE_UIDNEXT: + camel_imap4_summary_set_uidnext (folder->summary, resp->v.uidnext); + break; + case CAMEL_IMAP4_RESP_CODE_UIDVALIDITY: + camel_imap4_summary_set_uidvalidity (folder->summary, resp->v.uidvalidity); + break; + case CAMEL_IMAP4_RESP_CODE_UNSEEN: + camel_imap4_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_IMAP4_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_IMAP4_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_imap4_command_unref (ic); + + return retval; +} + + +static struct { + const char *name; + guint32 flag; +} imap4_capabilities[] = { + { "IMAP44", CAMEL_IMAP4_CAPABILITY_IMAP44 }, + { "IMAP44REV1", CAMEL_IMAP4_CAPABILITY_IMAP44REV1 }, + { "STATUS", CAMEL_IMAP4_CAPABILITY_STATUS }, + { "NAMESPACE", CAMEL_IMAP4_CAPABILITY_NAMESPACE }, + { "UIDPLUS", CAMEL_IMAP4_CAPABILITY_UIDPLUS }, + { "LITERAL+", CAMEL_IMAP4_CAPABILITY_LITERALPLUS }, + { "LOGINDISABLED", CAMEL_IMAP4_CAPABILITY_LOGINDISABLED }, + { "STARTTLS", CAMEL_IMAP4_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 (CamelIMAP4Engine *engine, int sentinel, CamelException *ex) +{ + camel_imap4_token_t token; + int i; + + engine->capa = CAMEL_IMAP4_CAPABILITY_utf8_search; + engine->level = 0; + + g_hash_table_foreach_remove (engine->authtypes, (GHRFunc) auth_free, NULL); + + if (camel_imap4_engine_next_token (engine, &token, ex) == -1) + return -1; + + while (token.token == CAMEL_IMAP4_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; imap4_capabilities[i].name; i++) { + if (!strcasecmp (imap4_capabilities[i].name, token.v.atom)) + engine->capa |= imap4_capabilities[i].flag; + } + } + + if (camel_imap4_engine_next_token (engine, &token, ex) == -1) + return -1; + } + + if (token.token != sentinel) { + camel_imap4_utils_set_unexpected_token_error (ex, engine, &token); + return -1; + } + + /* unget our sentinel token */ + camel_imap4_stream_unget_token (engine->istream, &token); + + /* figure out which version of IMAP4 we are dealing with */ + if (engine->capa & CAMEL_IMAP4_CAPABILITY_IMAP44REV1) { + engine->level = CAMEL_IMAP4_LEVEL_IMAP44REV1; + engine->capa |= CAMEL_IMAP4_CAPABILITY_STATUS; + } else if (engine->capa & CAMEL_IMAP4_CAPABILITY_IMAP44) { + engine->level = CAMEL_IMAP4_LEVEL_IMAP44; + } else { + engine->level = CAMEL_IMAP4_LEVEL_UNKNOWN; + } + + return 0; +} + +static int +engine_parse_flags_list (CamelIMAP4Engine *engine, CamelIMAP4RespCode *resp, int perm, CamelException *ex) +{ + guint32 flags = 0; + + if (camel_imap4_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 (CamelIMAP4Engine *engine, CamelException *ex) +{ + camel_imap4_token_t token; + + if (engine_parse_flags_list (engine, NULL, FALSE, ex) == -1) + return -1; + + if (camel_imap4_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_imap4_utils_set_unexpected_token_error (ex, engine, &token); + return -1; + } + + return 0; +} + + +enum { + IMAP4_STATUS_MESSAGES, + IMAP4_STATUS_RECENT, + IMAP4_STATUS_UIDNEXT, + IMAP4_STATUS_UIDVALIDITY, + IMAP4_STATUS_UNSEEN, + IMAP4_STATUS_UNKNOWN +}; + +static struct { + const char *name; + int type; +} imap4_status[] = { + { "MESSAGES", IMAP4_STATUS_MESSAGES }, + { "RECENT", IMAP4_STATUS_RECENT }, + { "UIDNEXT", IMAP4_STATUS_UIDNEXT }, + { "UIDVALIDITY", IMAP4_STATUS_UIDVALIDITY }, + { "UNSEEN", IMAP4_STATUS_UNSEEN }, + { NULL, IMAP4_STATUS_UNKNOWN }, +}; + +static int +engine_parse_status (CamelIMAP4Engine *engine, CamelException *ex) +{ + camel_imap4_token_t token; + char *mailbox; + size_t len; + int type; + + if (camel_imap4_engine_next_token (engine, &token, ex) == -1) + return -1; + + switch (token.token) { + case CAMEL_IMAP4_TOKEN_ATOM: + mailbox = g_strdup (token.v.atom); + break; + case CAMEL_IMAP4_TOKEN_QSTRING: + mailbox = g_strdup (token.v.qstring); + break; + case CAMEL_IMAP4_TOKEN_LITERAL: + if (camel_imap4_engine_literal (engine, (unsigned char **) &mailbox, &len, ex) == -1) + return -1; + break; + default: + fprintf (stderr, "Unexpected token in IMAP4 untagged STATUS response: %s%c\n", + token.token == CAMEL_IMAP4_TOKEN_NIL ? "NIL" : "", + (unsigned char) (token.token & 0xff)); + camel_imap4_utils_set_unexpected_token_error (ex, engine, &token); + return -1; + } + + if (camel_imap4_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_imap4_utils_set_unexpected_token_error (ex, engine, &token); + g_free (mailbox); + return -1; + } + + if (camel_imap4_engine_next_token (engine, &token, ex) == -1) { + g_free (mailbox); + return -1; + } + + while (token.token == CAMEL_IMAP4_TOKEN_ATOM) { + const unsigned char *inptr; + unsigned int v = 0; + + /* parse the status messages list */ + for (type = 0; imap4_status[type].name; type++) { + if (!strcasecmp (imap4_status[type].name, token.v.atom)) + break; + } + + if (type == IMAP4_STATUS_UNKNOWN) + fprintf (stderr, "unrecognized token in STATUS list: %s\n", token.v.atom); + + if (camel_imap4_engine_next_token (engine, &token, ex) == -1) { + g_free (mailbox); + return -1; + } + + if (token.token != CAMEL_IMAP4_TOKEN_ATOM) + break; + + if (type == IMAP4_STATUS_UIDNEXT || type == IMAP4_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 == IMAP4_STATUS_UNKNOWN) { + /* we'll let it slide... unget this token and continue */ + camel_imap4_stream_unget_token (engine->istream, &token); + goto loop; + } + + d(fprintf (stderr, "Encountered non-numeric token after %s in untagged STATUS response: %s\n", + imap4_status[type].name, token.v.atom)); + goto loop; + } + + switch (type) { + case IMAP4_STATUS_MESSAGES: + /* FIXME: save value */ + break; + case IMAP4_STATUS_RECENT: + /* FIXME: save value */ + break; + case IMAP4_STATUS_UNSEEN: + /* FIXME: save value */ + break; + default: + g_assert_not_reached (); + } + } + + loop: + if (camel_imap4_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_imap4_utils_set_unexpected_token_error (ex, engine, &token); + return -1; + } + + if (camel_imap4_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_imap4_utils_set_unexpected_token_error (ex, engine, &token); + return -1; + } + + return 0; +} + +static int +engine_parse_namespace (CamelIMAP4Engine *engine, CamelException *ex) +{ + CamelIMAP4Namespace *namespaces[3], *node, *tail; + camel_imap4_token_t token; + int i, n = 0; + + imap4_namespace_clear (&engine->namespaces.personal); + imap4_namespace_clear (&engine->namespaces.other); + imap4_namespace_clear (&engine->namespaces.shared); + + if (camel_imap4_engine_next_token (engine, &token, ex) == -1) + return -1; + + do { + namespaces[n] = NULL; + tail = (CamelIMAP4Namespace *) &namespaces[n]; + + if (token.token == '(') { + /* decode the list of namespace pairs */ + if (camel_imap4_engine_next_token (engine, &token, ex) == -1) + goto exception; + + while (token.token == '(') { + /* decode a namespace pair */ + + /* get the path name token */ + if (camel_imap4_engine_next_token (engine, &token, ex) == -1) + goto exception; + + if (token.token != CAMEL_IMAP4_TOKEN_QSTRING) { + d(fprintf (stderr, "Expected to find a qstring token as first element in NAMESPACE pair\n")); + camel_imap4_utils_set_unexpected_token_error (ex, engine, &token); + goto exception; + } + + node = g_new (CamelIMAP4Namespace, 1); + node->next = NULL; + node->path = g_strdup (token.v.qstring); + + /* get the path delimiter token */ + if (camel_imap4_engine_next_token (engine, &token, ex) == -1) { + g_free (node->path); + g_free (node); + + goto exception; + } + + if (token.token != CAMEL_IMAP4_TOKEN_QSTRING || strlen (token.v.qstring) > 1) { + d(fprintf (stderr, "Expected to find a qstring token as second element in NAMESPACE pair\n")); + camel_imap4_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'; + + /* canonicalise if this is an INBOX namespace */ + if (!g_ascii_strncasecmp (node->path, "INBOX", 5) && + (node->path[6] == '\0' || node->path[6] == node->sep)) + memcpy (node->path, "INBOX", 5); + + /* get the closing ')' for this namespace pair */ + if (camel_imap4_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_imap4_utils_set_unexpected_token_error (ex, engine, &token); + + goto exception; + } + + /* get the next token (should be either '(' or ')') */ + if (camel_imap4_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_imap4_utils_set_unexpected_token_error (ex, engine, &token); + goto exception; + } + } else if (token.token == CAMEL_IMAP4_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_imap4_utils_set_unexpected_token_error (ex, engine, &token); + goto exception; + } + + /* get the next token (should be either '(', NIL, or '\n') */ + if (camel_imap4_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++) + imap4_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*] + **/ + +static struct { + const char *name; + camel_imap4_resp_code_t code; + int save; +} imap4_resp_codes[] = { + { "ALERT", CAMEL_IMAP4_RESP_CODE_ALERT, 0 }, + { "BADCHARSET", CAMEL_IMAP4_RESP_CODE_BADCHARSET, 0 }, + { "CAPABILITY", CAMEL_IMAP4_RESP_CODE_CAPABILITY, 0 }, + { "PARSE", CAMEL_IMAP4_RESP_CODE_PARSE, 1 }, + { "PERMANENTFLAGS", CAMEL_IMAP4_RESP_CODE_PERM_FLAGS, 1 }, + { "READ-ONLY", CAMEL_IMAP4_RESP_CODE_READONLY, 1 }, + { "READ-WRITE", CAMEL_IMAP4_RESP_CODE_READWRITE, 1 }, + { "TRYCREATE", CAMEL_IMAP4_RESP_CODE_TRYCREATE, 1 }, + { "UIDNEXT", CAMEL_IMAP4_RESP_CODE_UIDNEXT, 1 }, + { "UIDVALIDITY", CAMEL_IMAP4_RESP_CODE_UIDVALIDITY, 1 }, + { "UNSEEN", CAMEL_IMAP4_RESP_CODE_UNSEEN, 1 }, + { "NEWNAME", CAMEL_IMAP4_RESP_CODE_NEWNAME, 1 }, + { "APPENDUID", CAMEL_IMAP4_RESP_CODE_APPENDUID, 1 }, + { "COPYUID", CAMEL_IMAP4_RESP_CODE_COPYUID, 1 }, + { NULL, CAMEL_IMAP4_RESP_CODE_UNKNOWN, 0 } +}; + + +int +camel_imap4_engine_parse_resp_code (CamelIMAP4Engine *engine, CamelException *ex) +{ + CamelIMAP4RespCode *resp = NULL; + camel_imap4_resp_code_t code; + camel_imap4_token_t token; + unsigned char *linebuf; + size_t len; + + if (camel_imap4_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_imap4_utils_set_unexpected_token_error (ex, engine, &token); + return -1; + } + + if (camel_imap4_engine_next_token (engine, &token, ex) == -1) + return -1; + + if (token.token != CAMEL_IMAP4_TOKEN_ATOM) { + d(fprintf (stderr, "Expected an atom token containing a RESP-CODE\n")); + camel_imap4_utils_set_unexpected_token_error (ex, engine, &token); + return -1; + } + + for (code = 0; imap4_resp_codes[code].name; code++) { + if (!strcmp (imap4_resp_codes[code].name, token.v.atom)) { + if (engine->current && imap4_resp_codes[code].save) { + resp = g_new0 (CamelIMAP4RespCode, 1); + resp->code = code; + } + break; + } + } + + switch (code) { + case CAMEL_IMAP4_RESP_CODE_BADCHARSET: + /* apparently we don't support UTF-8 afterall */ + engine->capa &= ~CAMEL_IMAP4_CAPABILITY_utf8_search; + break; + case CAMEL_IMAP4_RESP_CODE_CAPABILITY: + /* capability list follows */ + if (engine_parse_capability (engine, ']', ex) == -1) + goto exception; + break; + case CAMEL_IMAP4_RESP_CODE_PERM_FLAGS: + /* flag list follows */ + if (engine_parse_flags_list (engine, resp, TRUE, ex) == -1) + goto exception; + break; + case CAMEL_IMAP4_RESP_CODE_READONLY: + break; + case CAMEL_IMAP4_RESP_CODE_READWRITE: + break; + case CAMEL_IMAP4_RESP_CODE_TRYCREATE: + break; + case CAMEL_IMAP4_RESP_CODE_UIDNEXT: + if (camel_imap4_engine_next_token (engine, &token, ex) == -1) + goto exception; + + if (token.token != CAMEL_IMAP4_TOKEN_NUMBER) { + d(fprintf (stderr, "Expected an nz_number token as an argument to the UIDNEXT RESP-CODE\n")); + camel_imap4_utils_set_unexpected_token_error (ex, engine, &token); + goto exception; + } + + if (resp != NULL) + resp->v.uidnext = token.v.number; + + break; + case CAMEL_IMAP4_RESP_CODE_UIDVALIDITY: + if (camel_imap4_engine_next_token (engine, &token, ex) == -1) + goto exception; + + if (token.token != CAMEL_IMAP4_TOKEN_NUMBER) { + d(fprintf (stderr, "Expected an nz_number token as an argument to the UIDVALIDITY RESP-CODE\n")); + camel_imap4_utils_set_unexpected_token_error (ex, engine, &token); + goto exception; + } + + if (resp != NULL) + resp->v.uidvalidity = token.v.number; + + break; + case CAMEL_IMAP4_RESP_CODE_UNSEEN: + if (camel_imap4_engine_next_token (engine, &token, ex) == -1) + return -1; + + if (token.token != CAMEL_IMAP4_TOKEN_NUMBER) { + d(fprintf (stderr, "Expected an nz_number token as an argument to the UNSEEN RESP-CODE\n")); + camel_imap4_utils_set_unexpected_token_error (ex, engine, &token); + goto exception; + } + + if (resp != NULL) + resp->v.unseen = token.v.number; + + break; + case CAMEL_IMAP4_RESP_CODE_NEWNAME: + /* this RESP-CODE may actually be removed - see here: + * http://www.washington.edu/imap4/listarch/2001/msg00058.html */ + + if (camel_imap4_engine_next_token (engine, &token, ex) == -1) + return -1; + + if (token.token != CAMEL_IMAP4_TOKEN_ATOM && token.token != CAMEL_IMAP4_TOKEN_QSTRING) { + d(fprintf (stderr, "Expected an atom or qstring token as the first argument to the NEWNAME RESP-CODE\n")); + camel_imap4_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_IMAP4_TOKEN_ATOM && token.token != CAMEL_IMAP4_TOKEN_QSTRING) { + d(fprintf (stderr, "Expected an atom or qstring token as the second argument to the NEWNAME RESP-CODE\n")); + camel_imap4_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_IMAP4_RESP_CODE_APPENDUID: + if (camel_imap4_engine_next_token (engine, &token, ex) == -1) + return -1; + + if (token.token != CAMEL_IMAP4_TOKEN_NUMBER) { + d(fprintf (stderr, "Expected an nz_number token as the first argument to the APPENDUID RESP-CODE\n")); + camel_imap4_utils_set_unexpected_token_error (ex, engine, &token); + goto exception; + } + + if (resp != NULL) + resp->v.appenduid.uidvalidity = token.v.number; + + if (camel_imap4_engine_next_token (engine, &token, ex) == -1) + return -1; + + if (token.token != CAMEL_IMAP4_TOKEN_NUMBER) { + d(fprintf (stderr, "Expected an nz_number token as the second argument to the APPENDUID RESP-CODE\n")); + camel_imap4_utils_set_unexpected_token_error (ex, engine, &token); + goto exception; + } + + if (resp != NULL) + resp->v.appenduid.uid = token.v.number; + + break; + case CAMEL_IMAP4_RESP_CODE_COPYUID: + if (camel_imap4_engine_next_token (engine, &token, ex) == -1) + return -1; + + if (token.token != CAMEL_IMAP4_TOKEN_NUMBER) { + d(fprintf (stderr, "Expected an nz_number token as the first argument to the COPYUID RESP-CODE\n")); + camel_imap4_utils_set_unexpected_token_error (ex, engine, &token); + goto exception; + } + + if (resp != NULL) + resp->v.copyuid.uidvalidity = token.v.number; + + if (camel_imap4_engine_next_token (engine, &token, ex) == -1) + return -1; + + if (token.token != CAMEL_IMAP4_TOKEN_ATOM) { + d(fprintf (stderr, "Expected an atom token as the second argument to the COPYUID RESP-CODE\n")); + camel_imap4_utils_set_unexpected_token_error (ex, engine, &token); + goto exception; + } + + if (resp != NULL) + resp->v.copyuid.srcset = g_strdup (token.v.atom); + + if (camel_imap4_engine_next_token (engine, &token, ex) == -1) + return -1; + + if (token.token != CAMEL_IMAP4_TOKEN_ATOM) { + d(fprintf (stderr, "Expected an atom token as the third argument to the APPENDUID RESP-CODE\n")); + camel_imap4_utils_set_unexpected_token_error (ex, engine, &token); + goto exception; + } + + 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*] "]" */ + + /* eat up the TEXT_CHARs */ + while (token.token != ']' && token.token != '\n') { + if (camel_imap4_engine_next_token (engine, &token, ex) == -1) + goto exception; + } + + break; + } + + while (token.token != ']' && token.token != '\n') { + if (camel_imap4_engine_next_token (engine, &token, ex) == -1) + goto exception; + } + + if (token.token != ']') { + camel_imap4_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_IMAP4_RESP_CODE_ALERT) { + if (camel_imap4_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_IMAP4_RESP_CODE_PARSE) { + if (camel_imap4_engine_line (engine, &linebuf, &len, ex) == -1) + goto exception; + + resp->v.parse = linebuf; + } else { + /* eat up the rest of the response */ + if (camel_imap4_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_imap4_resp_code_free (resp); + + return -1; +} + + + +/* returns -1 on error, or one of CAMEL_IMAP4_UNTAGGED_[OK,NO,BAD,PREAUTH,HANDLED] on success */ +int +camel_imap4_engine_handle_untagged_1 (CamelIMAP4Engine *engine, camel_imap4_token_t *token, CamelException *ex) +{ + int code = CAMEL_IMAP4_UNTAGGED_HANDLED; + CamelIMAP4Command *ic = engine->current; + CamelIMAP4UntaggedCallback untagged; + CamelFolder *folder; + unsigned int v; + + if (camel_imap4_engine_next_token (engine, token, ex) == -1) + return -1; + + if (token->token == CAMEL_IMAP4_TOKEN_ATOM) { + if (!strcmp ("BYE", token->v.atom)) { + /* we don't care if we fail here, either way we've been disconnected */ + camel_imap4_engine_parse_resp_code (engine, NULL); + engine->state = CAMEL_IMAP4_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, + _("IMAP4 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_imap4_engine_next_token (engine, token, ex) == -1) + return -1; + + if (token->token != '\n') { + camel_imap4_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_IMAP4_UNTAGGED_NO : CAMEL_IMAP4_UNTAGGED_BAD; + + /* our command has been rejected */ + if (camel_imap4_engine_next_token (engine, token, ex) == -1) + return -1; + + if (token->token == '[') { + /* we have a resp code */ + camel_imap4_stream_unget_token (engine->istream, token); + if (camel_imap4_engine_parse_resp_code (engine, ex) == -1) + return -1; + } else if (token->token != '\n') { + /* we just have resp text */ + if (camel_imap4_engine_line (engine, NULL, NULL, ex) == -1) + return -1; + } + } else if (!strcmp ("OK", token->v.atom)) { + code = CAMEL_IMAP4_UNTAGGED_OK; + + if (engine->state == CAMEL_IMAP4_ENGINE_CONNECTED) { + /* initial server greeting */ + engine->state = CAMEL_IMAP4_ENGINE_PREAUTH; + } + + if (camel_imap4_engine_next_token (engine, token, ex) == -1) + return -1; + + if (token->token == '[') { + /* we have a resp code */ + camel_imap4_stream_unget_token (engine->istream, token); + if (camel_imap4_engine_parse_resp_code (engine, ex) == -1) + return -1; + } else { + /* we just have resp text */ + if (camel_imap4_engine_line (engine, NULL, NULL, ex) == -1) + return -1; + } + } else if (!strcmp ("PREAUTH", token->v.atom)) { + code = CAMEL_IMAP4_UNTAGGED_PREAUTH; + + if (engine->state == CAMEL_IMAP4_ENGINE_CONNECTED) + engine->state = CAMEL_IMAP4_ENGINE_AUTHENTICATED; + + if (camel_imap4_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 imap4 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_imap4_engine_eat_line (engine, ex) == -1) + return -1; + } + } else if (token->token == CAMEL_IMAP4_TOKEN_NUMBER) { + /* we probably have something like "* 1 EXISTS" */ + v = token->v.number; + + if (camel_imap4_engine_next_token (engine, token, ex) == -1) + return -1; + + if (token->token != CAMEL_IMAP4_TOKEN_ATOM) { + camel_imap4_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_imap4_summary_set_exists (folder->summary, v); + } else if (!strcmp ("EXPUNGE", token->v.atom)) { + camel_imap4_summary_expunge (folder->summary, (int) v); + } else if (!strcmp ("RECENT", token->v.atom)) { + camel_imap4_summary_set_recent (folder->summary, v); + } else if (ic && (untagged = g_hash_table_lookup (ic->untagged, token->v.atom))) { + /* registered untagged handler for imap4 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_imap4_engine_eat_line (engine, ex) == -1) + return -1; + } else { + camel_imap4_utils_set_unexpected_token_error (ex, engine, token); + + return -1; + } + + return code; +} + + +void +camel_imap4_engine_handle_untagged (CamelIMAP4Engine *engine, CamelException *ex) +{ + camel_imap4_token_t token; + + g_return_if_fail (CAMEL_IS_IMAP4_ENGINE (engine)); + + do { + if (camel_imap4_engine_next_token (engine, &token, ex) == -1) + goto exception; + + if (token.token != '*') + break; + + if (camel_imap4_engine_handle_untagged_1 (engine, &token, ex) == -1) + goto exception; + } while (1); + + camel_imap4_stream_unget_token (engine->istream, &token); + + return; + + exception: + + engine->state = CAMEL_IMAP4_ENGINE_DISCONNECTED; +} + + +static int +imap4_process_command (CamelIMAP4Engine *engine, CamelIMAP4Command *ic) +{ + int retval; + + while ((retval = camel_imap4_command_step (ic)) == 0) + ; + + if (retval == -1) { + engine->state = CAMEL_IMAP4_ENGINE_DISCONNECTED; + return -1; + } + + return 0; +} + + +static void +engine_prequeue_folder_select (CamelIMAP4Engine *engine) +{ + CamelIMAP4Command *ic; + const char *cmd; + + ic = (CamelIMAP4Command *) 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_imap4_command_new (engine, ic->folder, "SELECT %F\r\n", ic->folder); + camel_imap4_engine_prequeue (engine, ic); + ic->user_data = engine; + + camel_imap4_command_unref (ic); +} + + +static int +engine_state_change (CamelIMAP4Engine *engine, CamelIMAP4Command *ic) +{ + const char *cmd; + int retval = 0; + + cmd = ic->parts->buffer; + if (!strncmp (cmd, "SELECT ", 7) || !strncmp (cmd, "EXAMINE ", 8)) { + if (ic->result == CAMEL_IMAP4_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_IMAP4_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_IMAP4_RESULT_OK) + engine->state = CAMEL_IMAP4_ENGINE_AUTHENTICATED; + } else if (!strncmp (cmd, "LOGOUT", 6)) { + engine->state = CAMEL_IMAP4_ENGINE_DISCONNECTED; + } + + return retval; +} + +/** + * camel_imap4_engine_iterate: + * @engine: IMAP4 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 + * CamelIMAP4Command that failed. + **/ +int +camel_imap4_engine_iterate (CamelIMAP4Engine *engine) +{ + CamelIMAP4Command *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 = (CamelIMAP4Command *) e_dlist_remhead (&engine->queue); + ic->status = CAMEL_IMAP4_COMMAND_ACTIVE; + + if (imap4_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 imap4 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 = (CamelIMAP4Command *) 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_imap4_command_unref (ic); + ic = nic; + } + + retval = ic->id; + } + + camel_imap4_command_unref (ic); + + return retval; +} + + +/** + * camel_imap4_engine_queue: + * @engine: IMAP4 engine + * @folder: IMAP4 folder that the command will affect (or %NULL if it doesn't matter) + * @format: command format + * @Varargs: arguments + * + * Basically the same as #camel_imap4_command_new() except that this + * function also places the command in the engine queue. + * + * Returns the CamelIMAP4Command. + **/ +CamelIMAP4Command * +camel_imap4_engine_queue (CamelIMAP4Engine *engine, CamelFolder *folder, const char *format, ...) +{ + CamelIMAP4Command *ic; + va_list args; + + va_start (args, format); + ic = camel_imap4_command_newv (engine, (CamelIMAP4Folder *) folder, format, args); + va_end (args); + + ic->id = engine->nextid++; + e_dlist_addtail (&engine->queue, (EDListNode *) ic); + camel_imap4_command_ref (ic); + + return ic; +} + + +/** + * camel_imap4_engine_prequeue: + * @engine: IMAP4 engine + * @ic: IMAP4 command to pre-queue + * + * Places @ic at the head of the queue of pending IMAP4 commands. + **/ +void +camel_imap4_engine_prequeue (CamelIMAP4Engine *engine, CamelIMAP4Command *ic) +{ + g_return_if_fail (CAMEL_IS_IMAP4_ENGINE (engine)); + g_return_if_fail (ic != NULL); + + camel_imap4_command_ref (ic); + + if (e_dlist_empty (&engine->queue)) { + e_dlist_addtail (&engine->queue, (EDListNode *) ic); + ic->id = engine->nextid++; + } else { + CamelIMAP4Command *nic; + EDListNode *node; + + node = (EDListNode *) ic; + e_dlist_addhead (&engine->queue, node); + nic = (CamelIMAP4Command *) node->next; + ic->id = nic->id - 1; + + if (ic->id == 0) { + /* increment all command ids */ + node = engine->queue.head; + while (node->next) { + nic = (CamelIMAP4Command *) node; + node = node->next; + nic->id++; + } + } + } +} + + +void +camel_imap4_engine_dequeue (CamelIMAP4Engine *engine, CamelIMAP4Command *ic) +{ + EDListNode *node = (EDListNode *) ic; + + if (node->next == NULL && node->prev == NULL) + return; + + e_dlist_remove (node); + node->next = NULL; + node->prev = NULL; + + camel_imap4_command_unref (ic); +} + + +int +camel_imap4_engine_next_token (CamelIMAP4Engine *engine, camel_imap4_token_t *token, CamelException *ex) +{ + if (camel_imap4_stream_next_token (engine->istream, token) == -1) { + camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM, + _("IMAP4 server %s unexpectedly disconnected: %s"), + engine->url->host, errno ? g_strerror (errno) : _("Unknown")); + return -1; + } + + return 0; +} + + +int +camel_imap4_engine_eat_line (CamelIMAP4Engine *engine, CamelException *ex) +{ + camel_imap4_token_t token; + unsigned char *literal; + int retval; + size_t n; + + do { + if (camel_imap4_engine_next_token (engine, &token, ex) == -1) + return -1; + + if (token.token == CAMEL_IMAP4_TOKEN_LITERAL) { + while ((retval = camel_imap4_stream_literal (engine->istream, &literal, &n)) == 1) + ; + + if (retval == -1) { + camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM, + _("IMAP4 server %s unexpectedly disconnected: %s"), + engine->url->host, errno ? g_strerror (errno) : _("Unknown")); + + return -1; + } + } + } while (token.token != '\n'); + + return 0; +} + + +int +camel_imap4_engine_line (CamelIMAP4Engine *engine, unsigned char **line, size_t *len, CamelException *ex) +{ + GByteArray *linebuf = NULL; + unsigned char *buf; + size_t buflen; + int retval; + + if (line != NULL) + linebuf = g_byte_array_new (); + + while ((retval = camel_imap4_stream_line (engine->istream, &buf, &buflen)) > 0) { + if (linebuf != NULL) + g_byte_array_append (linebuf, buf, buflen); + } + + if (retval == -1) { + camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM, + _("IMAP4 server %s unexpectedly disconnected: %s"), + engine->url->host, errno ? g_strerror (errno) : _("Unknown")); + + if (linebuf != NULL) + g_byte_array_free (linebuf, TRUE); + + return -1; + } + + if (linebuf != NULL) { + g_byte_array_append (linebuf, buf, buflen); + + *line = linebuf->data; + *len = linebuf->len; + + g_byte_array_free (linebuf, FALSE); + } + + return 0; +} + + +int +camel_imap4_engine_literal (CamelIMAP4Engine *engine, unsigned char **literal, size_t *len, CamelException *ex) +{ + GByteArray *literalbuf = NULL; + unsigned char *buf; + size_t buflen; + int retval; + + if (literal != NULL) + literalbuf = g_byte_array_new (); + + while ((retval = camel_imap4_stream_literal (engine->istream, &buf, &buflen)) > 0) { + if (literalbuf != NULL) + g_byte_array_append (literalbuf, buf, buflen); + } + + if (retval == -1) { + camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM, + _("IMAP4 server %s unexpectedly disconnected: %s"), + engine->url->host, errno ? g_strerror (errno) : _("Unknown")); + + if (literalbuf != NULL) + g_byte_array_free (literalbuf, TRUE); + + return -1; + } + + if (literalbuf != NULL) { + g_byte_array_append (literalbuf, buf, buflen); + g_byte_array_append (literalbuf, "", 1); + + *literal = literalbuf->data; + *len = literalbuf->len - 1; + + g_byte_array_free (literalbuf, FALSE); + } + + return 0; +} + + +void +camel_imap4_resp_code_free (CamelIMAP4RespCode *rcode) +{ + switch (rcode->code) { + case CAMEL_IMAP4_RESP_CODE_PARSE: + g_free (rcode->v.parse); + break; + case CAMEL_IMAP4_RESP_CODE_NEWNAME: + g_free (rcode->v.newname[0]); + g_free (rcode->v.newname[1]); + break; + case CAMEL_IMAP4_RESP_CODE_COPYUID: + g_free (rcode->v.copyuid.srcset); + g_free (rcode->v.copyuid.destset); + break; + default: + break; + } + + g_free (rcode); +} -- cgit