aboutsummaryrefslogtreecommitdiffstats
path: root/camel/providers/imapp/camel-imapp-engine.c
diff options
context:
space:
mode:
Diffstat (limited to 'camel/providers/imapp/camel-imapp-engine.c')
-rw-r--r--camel/providers/imapp/camel-imapp-engine.c1156
1 files changed, 1156 insertions, 0 deletions
diff --git a/camel/providers/imapp/camel-imapp-engine.c b/camel/providers/imapp/camel-imapp-engine.c
new file mode 100644
index 0000000000..f6cfcbf86c
--- /dev/null
+++ b/camel/providers/imapp/camel-imapp-engine.c
@@ -0,0 +1,1156 @@
+
+#include "config.h"
+
+#include <stdio.h>
+#include <string.h>
+
+#include "camel-imapp-engine.h"
+#include "camel-imapp-stream.h"
+#include "camel-imapp-utils.h"
+#include "camel-imapp-exception.h"
+
+#include <camel/camel-folder-summary.h>
+#include <camel/camel-stream-mem.h>
+#include <camel/camel-stream-null.h>
+#include <camel/camel-data-wrapper.h>
+#include <camel/camel-sasl.h>
+
+#include <ctype.h>
+
+#define e(x)
+#define c(x) /* command build debug */
+
+static void imap_engine_command_addv(CamelIMAPPEngine *imap, CamelIMAPPCommand *ic, const char *fmt, va_list ap);
+static void imap_engine_command_complete(CamelIMAPPEngine *imap, CamelIMAPPCommand *ic);
+
+struct _handler {
+ CamelIMAPPEngineFunc func;
+ void *data;
+ char name[1];
+};
+
+static void
+class_init(CamelIMAPPEngineClass *ieclass)
+{
+ ieclass->tagprefix = 'A';
+
+ camel_object_class_add_event((CamelObjectClass *)ieclass, "status", NULL);
+}
+
+static void
+object_init(CamelIMAPPEngine *ie, CamelIMAPPEngineClass *ieclass)
+{
+ ie->handlers = g_hash_table_new(g_str_hash, g_str_equal);
+ e_dlist_init(&ie->active);
+ e_dlist_init(&ie->queue);
+
+ ie->tagprefix = ieclass->tagprefix;
+ ieclass->tagprefix++;
+ if (ieclass->tagprefix > 'Z')
+ ieclass->tagprefix = 'A';
+ ie->tagprefix = 'A';
+
+ ie->state = IMAP_ENGINE_DISCONNECT;
+}
+
+static void
+handler_free(void *key, void *mem, void *data)
+{
+ g_free(mem);
+}
+
+static void
+object_finalise(CamelIMAPPEngine *ie, CamelIMAPPEngineClass *ieclass)
+{
+ /* FIXME: need to free the commands ... */
+ while (camel_imapp_engine_iterate(ie, NULL) > 0)
+ ;
+
+ g_hash_table_foreach(ie->handlers, (GHFunc)handler_free, NULL);
+ g_hash_table_destroy(ie->handlers);
+}
+
+CamelType
+camel_imapp_engine_get_type (void)
+{
+ static CamelType type = CAMEL_INVALID_TYPE;
+
+ if (type == CAMEL_INVALID_TYPE) {
+ type = camel_type_register (
+ camel_object_get_type (),
+ "CamelIMAPPEngine",
+ sizeof (CamelIMAPPEngine),
+ sizeof (CamelIMAPPEngineClass),
+ (CamelObjectClassInitFunc) class_init,
+ NULL,
+ (CamelObjectInitFunc) object_init,
+ (CamelObjectFinalizeFunc) object_finalise);
+ }
+
+ return type;
+}
+
+/* FIXME: check this, just taken from old code, not rfc */
+struct {
+ char *name;
+ guint32 flag;
+} capa_table[] = {
+ { "IMAP4", IMAP_CAPABILITY_IMAP4 },
+ { "IMAP4REV1", IMAP_CAPABILITY_IMAP4REV1 },
+ { "STATUS", IMAP_CAPABILITY_STATUS } ,
+ { "NAMESPACE", IMAP_CAPABILITY_NAMESPACE },
+ { "UIDPLUS", IMAP_CAPABILITY_UIDPLUS },
+ { "LITERAL+", IMAP_CAPABILITY_LITERALPLUS },
+ { "STARTTLS", IMAP_CAPABILITY_STARTTLS },
+};
+
+
+/*
+capability_data ::= "CAPABILITY" SPACE [1#capability SPACE] "IMAP4rev1"
+ [SPACE 1#capability]
+ ;; IMAP4rev1 servers which offer RFC 1730
+ ;; compatibility MUST list "IMAP4" as the first
+ ;; capability.
+*/
+static int resp_capability(CamelIMAPPEngine *ie, guint32 id, void *data)
+{
+ int tok, len, i;
+ unsigned char *token, *p, c;
+
+ /* FIXME: handle auth types */
+
+ printf("got capability response:\n");
+ while (1) {
+ tok = camel_imapp_stream_token(ie->stream, &token, &len);
+ switch(tok) {
+ case IMAP_TOK_TOKEN:
+ p = token;
+ while ((c = *p))
+ *p++ = toupper(c);
+ case IMAP_TOK_INT:
+ printf(" cap: '%s'\n", token);
+ for (i=0;i<(int)(sizeof(capa_table)/sizeof(capa_table[0]));i++)
+ if (strcmp(token, capa_table[i].name))
+ ie->capa |= capa_table[i].flag;
+ break;
+ case '\n':
+ return 0;
+ case IMAP_TOK_ERROR:
+ case IMAP_TOK_PROTOCOL:
+ camel_imapp_engine_skip(ie);
+ return -1;
+ default:
+ printf("Unknown Response token %02x '%c'\n", tok, isprint(tok)?tok:'.');
+ }
+ } while (tok != '\n');
+
+ return 0;
+}
+
+/* expunge command, id is expunged seq number */
+/* message_data ::= nz_number SPACE ("EXPUNGE" /
+ ("FETCH" SPACE msg_att)) */
+static int resp_expunge(CamelIMAPPEngine *ie, guint32 id, void *data)
+{
+ printf("message expunged: %d\n", id);
+
+ return camel_imapp_engine_skip(ie);
+}
+
+static int resp_flags(CamelIMAPPEngine *ie, guint32 id, void *data)
+{
+ guint32 flags;
+
+ imap_parse_flags(ie->stream, &flags);
+
+ printf("flags: %08x\n", flags);
+
+ return camel_imapp_engine_skip(ie);
+}
+
+/* exists count */
+static int resp_exists(CamelIMAPPEngine *ie, guint32 id, void *data)
+{
+ printf("messages exist: %d\n", id);
+
+ if (ie->select_response)
+ ie->select_response->exists = id;
+
+ return camel_imapp_engine_skip(ie);
+}
+
+static int resp_recent(CamelIMAPPEngine *ie, guint32 id, void *data)
+{
+ printf("messages recent: %d\n", id);
+
+ if (ie->select_response)
+ ie->select_response->recent = id;
+
+ return camel_imapp_engine_skip(ie);
+}
+
+static int resp_fetch(CamelIMAPPEngine *ie, guint32 id, void *data)
+{
+ struct _fetch_info *finfo;
+
+ finfo = imap_parse_fetch(ie->stream);
+ imap_dump_fetch(finfo);
+ imap_free_fetch(finfo);
+
+ return camel_imapp_engine_skip(ie);
+}
+
+#if 0
+static int resp_list(CamelIMAPPEngine *ie, guint32 id, void *data)
+{
+ struct _list_info *linfo;
+
+ linfo = imap_parse_list(ie->stream);
+ printf("list: '%s' (%c)\n", linfo->name, linfo->separator);
+ imap_free_list(linfo);
+
+ return camel_imapp_engine_skip(ie);
+}
+#endif
+
+CamelIMAPPEngine *
+camel_imapp_engine_new(CamelIMAPPStream *stream)
+{
+ CamelIMAPPEngine * volatile engine;
+
+ engine = CAMEL_IMAPP_ENGINE (camel_object_new (CAMEL_IMAPP_ENGINE_TYPE));
+ engine->stream = stream;
+ camel_object_ref((CamelObject *)stream);
+
+ camel_imapp_engine_add_handler(engine, "CAPABILITY", resp_capability, engine);
+
+ /* mailbox_data */
+ camel_imapp_engine_add_handler(engine, "FLAGS", (CamelIMAPPEngineFunc)resp_flags, engine);
+ camel_imapp_engine_add_handler(engine, "EXISTS", (CamelIMAPPEngineFunc)resp_exists, engine);
+ camel_imapp_engine_add_handler(engine, "RECENT", (CamelIMAPPEngineFunc)resp_recent, engine);
+
+#if 0
+ camel_imapp_engine_add_handler(engine, "LIST", (CamelIMAPPEngineFunc)resp_list, engine);
+ camel_imapp_engine_add_handler(engine, "LSUB", (CamelIMAPPEngineFunc)resp_list, engine);
+#endif
+ /* message_data */
+ camel_imapp_engine_add_handler(engine, "EXPUNGE", (CamelIMAPPEngineFunc)resp_expunge, engine);
+ camel_imapp_engine_add_handler(engine, "FETCH", (CamelIMAPPEngineFunc)resp_fetch, engine);
+
+ /* TODO: move this to a driver:connect call? */
+ CAMEL_TRY {
+ unsigned char *token;
+ unsigned int len;
+ int tok;
+
+ tok = camel_imapp_stream_token(stream, &token, &len);
+ if (tok == '*') {
+ struct _status_info *sinfo = imap_parse_status(stream);
+
+ switch (sinfo->result) {
+ case IMAP_OK:
+ engine->state = IMAP_ENGINE_CONNECT;
+ printf("Server connected ok: %s\n", sinfo->text);
+ break;
+ case IMAP_PREAUTH:
+ printf("pre-authenticated ...\n");
+ engine->state = IMAP_ENGINE_AUTH;
+ break;
+ default:
+ imap_free_status(sinfo);
+ camel_exception_throw(1, "Server refused connection: %s", sinfo->text);
+ break;
+ }
+ imap_free_status(sinfo);
+ } else {
+ engine->state = IMAP_ENGINE_CONNECT;
+ printf("unknwon server greeting, ignored\n");
+ camel_imapp_engine_skip(engine);
+ }
+ camel_imapp_engine_capabilities(engine);
+ } CAMEL_CATCH(ex) {
+ printf("connection failed: %s\n", ex->desc);
+ camel_object_unref((CamelObject *)engine);
+ engine = NULL;
+ } CAMEL_DONE;
+
+ return engine;
+}
+
+void
+camel_imapp_engine_add_handler(CamelIMAPPEngine *imap, const char *response, CamelIMAPPEngineFunc func, void *data)
+{
+ struct _handler *h;
+ const unsigned char *p;
+ unsigned char *o, c;
+
+ h = g_malloc0(sizeof(*h) + strlen(response));
+ h->func = func;
+ h->data = data;
+
+ p = response;
+ o = h->name;
+ while ((c = *p++))
+ *o++ = toupper(c);
+ *o = 0;
+
+ g_hash_table_insert(imap->handlers, h->name, h);
+}
+
+int
+camel_imapp_engine_capabilities(CamelIMAPPEngine *ie)
+{
+ CamelIMAPPCommand *ic;
+
+ /* reset capabilities */
+ ie->capa = 0;
+
+ ic = camel_imapp_engine_command_new(ie, "CAPABILITY", NULL, "CAPABILITY");
+ camel_imapp_engine_command_queue(ie, ic);
+ while (camel_imapp_engine_iterate(ie, ic)>0)
+ ;
+ camel_imapp_engine_command_free(ie, ic);
+
+ return 0;
+}
+
+/* skip the rest of the line of tokens */
+int
+camel_imapp_engine_skip(CamelIMAPPEngine *imap)
+{
+ int tok;
+ unsigned char *token;
+ unsigned int len;
+
+ do {
+ tok = camel_imapp_stream_token(imap->stream, &token, &len);
+ if (tok == IMAP_TOK_LITERAL) {
+ camel_imapp_stream_set_literal(imap->stream, len);
+ while ((tok = camel_imapp_stream_getl(imap->stream, &token, &len)) > 0) {
+ printf("Skip literal data '%.*s'\n", (int)len, token);
+ }
+ }
+ } while (tok != '\n' && tok >= 0);
+
+ if (tok < 0)
+ return -1;
+
+ return 0;
+}
+
+/* handle any untagged responses */
+int
+iterate_untagged(CamelIMAPPEngine *imap)
+{
+ unsigned int id, len;
+ unsigned char *token, *p, c;
+ int tok;
+ struct _handler *h;
+ struct _status_info *sinfo;
+
+ e(printf("got untagged response\n"));
+ id = 0;
+ tok = camel_imapp_stream_token(imap->stream, &token, &len);
+ if (tok == IMAP_TOK_INT) {
+ id = strtoul(token, NULL, 10);
+ tok = camel_imapp_stream_token(imap->stream, &token, &len);
+ }
+
+ if (tok == '\n')
+ camel_exception_throw(1, "truncated server response");
+
+ e(printf("Have token '%s' id %d\n", token, id));
+ p = token;
+ while ((c = *p))
+ *p++ = toupper(c);
+
+ /* first, check for generic unsolicited response */
+ h = g_hash_table_lookup(imap->handlers, token);
+ if (h) {
+ tok = h->func(imap, id, h->data);
+ if (tok < 0)
+ return tok;
+ return 1;
+ }
+
+ /* TODO: apart from bye/preauth, these could be callbacks/events? */
+
+ /* now, check for status responses */
+ switch (imap_tokenise(token, len)) {
+ case IMAP_BYE:
+ case IMAP_OK:
+ case IMAP_NO:
+ case IMAP_BAD:
+ case IMAP_PREAUTH:
+ /* TODO: validate which ones of these can happen as unsolicited responses */
+ /* TODO: handle bye/preauth differently */
+ /* FIXME: free sinfo */
+ camel_imapp_stream_ungettoken(imap->stream, tok, token, len);
+ sinfo = imap_parse_status(imap->stream);
+ camel_object_trigger_event(imap, "status", sinfo);
+ imap_free_status(sinfo);
+#if 0
+ switch(sinfo->condition) {
+ case IMAP_READ_WRITE:
+ printf("folder is read-write\n");
+ break;
+ case IMAP_READ_ONLY:
+ printf("folder is read-only\n");
+ break;
+ case IMAP_UIDVALIDITY:
+ if (imap->select_response)
+ imap->select_response->uidvalidity = sinfo->u.uidvalidity;
+ break;
+#if 0
+ /* not defined yet ... */
+ case IMAP_UIDNEXT:
+ printf("got uidnext for folder: %d\n", sinfo->u.uidnext);
+ break;
+#endif
+ case IMAP_UNSEEN:
+ if (imap->select_response)
+ imap->select_response->unseen = sinfo->u.unseen;
+ break;
+ case IMAP_PERMANENTFLAGS:
+ if (imap->select_response)
+ imap->select_response->permanentflags = sinfo->u.permanentflags;
+ break;
+ case IMAP_ALERT:
+ printf("ALERT!: %s\n", sinfo->text);
+ break;
+ case IMAP_PARSE:
+ printf("PARSE: %s\n", sinfo->text);
+ break;
+ default:
+ break;
+ }
+#endif
+ break;
+ default:
+ printf("unknown token: %s\n", token);
+ camel_imapp_engine_skip(imap);
+ /* unknown response, just ignore it */
+ }
+
+ return 1;
+}
+
+/* handle any continuation requests
+ either data continuations, or auth continuation */
+int
+iterate_continuation(CamelIMAPPEngine *imap)
+{
+ CamelIMAPPCommand *ic;
+ CamelIMAPPCommandPart *cp;
+
+ printf("got continuation response\n");
+
+ ic = imap->literal;
+ imap->literal = NULL;
+ if (ic == NULL) {
+ camel_imapp_engine_skip(imap);
+ printf("got continuation response with no outstanding continuation requests?\n");
+ return 1;
+ }
+
+ printf("got continuation response for data\n");
+ cp = ic->current;
+ switch(cp->type & CAMEL_IMAPP_COMMAND_MASK) {
+ case CAMEL_IMAPP_COMMAND_DATAWRAPPER:
+ printf("writing data wrapper to literal\n");
+ camel_data_wrapper_write_to_stream((CamelDataWrapper *)cp->ob, (CamelStream *)imap->stream);
+ break;
+ case CAMEL_IMAPP_COMMAND_STREAM:
+ printf("writing stream to literal\n");
+ camel_stream_write_to_stream((CamelStream *)cp->ob, (CamelStream *)imap->stream);
+ break;
+ case CAMEL_IMAPP_COMMAND_AUTH: {
+ CamelException *ex = camel_exception_new();
+ char *resp;
+ unsigned char *token;
+ int tok, len;
+
+ tok = camel_imapp_stream_token(imap->stream, &token, &len);
+ resp = camel_sasl_challenge_base64((CamelSasl *)cp->ob, token, ex);
+ if (camel_exception_is_set(ex))
+ camel_exception_throw_ex(ex);
+ camel_exception_free(ex);
+
+ printf("got auth continuation, feeding token '%s' back to auth mech\n", resp);
+
+ camel_stream_write((CamelStream *)imap->stream, resp, strlen(resp));
+
+ /* we want to keep getting called until we get a status reponse from the server
+ ignore what sasl tells us */
+ imap->literal = ic;
+
+ break; }
+ default:
+ /* should we just ignore? */
+ camel_exception_throw(1, "continuation response for non-continuation request");
+ }
+
+ camel_imapp_engine_skip(imap);
+
+ cp = cp->next;
+ if (cp->next) {
+ ic->current = cp;
+ printf("next part of command \"A%05u: %s\"\n", ic->tag, cp->data);
+ camel_stream_printf((CamelStream *)imap->stream, "%s\r\n", cp->data);
+ if (cp->type & CAMEL_IMAPP_COMMAND_CONTINUATION) {
+ imap->literal = ic;
+ } else {
+ g_assert(cp->next->next == NULL);
+ }
+ } else {
+ printf("%p: queueing continuation\n", ic);
+ camel_stream_printf((CamelStream *)imap->stream, "\r\n");
+ }
+
+ if (imap->literal == NULL) {
+ ic = (CamelIMAPPCommand *)e_dlist_remhead(&imap->queue);
+ if (ic) {
+ printf("found outstanding op, queueing\n");
+ camel_imapp_engine_command_queue(imap, ic);
+ }
+ }
+
+ return 1;
+}
+
+/* handle a completion line */
+int
+iterate_completion(CamelIMAPPEngine *imap, unsigned char *token)
+{
+ CamelIMAPPCommand *ic;
+ unsigned int tag;
+
+ if (token[0] != imap->tagprefix)
+ camel_exception_throw(1, "Server sent unexpected response: %s", token);
+
+ tag = strtoul(token+1, NULL, 10);
+ ic = camel_imapp_engine_command_find_tag(imap, tag);
+ if (ic) {
+ printf("Got completion response for command %05u '%s'\n", ic->tag, ic->name);
+ printf("%p: removing command from qwueue, we were at '%s'\n", ic, ic->current->data);
+ printf("%p: removing command\n", ic);
+ e_dlist_remove((EDListNode *)ic);
+ if (imap->literal == ic)
+ imap->literal = NULL;
+ ic->status = imap_parse_status(imap->stream);
+ printf("got response code: %s\n", ic->status->text);
+
+ /* TODO: remove this stuff and use a completion handler? */
+ /* TODO: handle 'SELECT' command cleanup here */
+ /* FIXME: have this use tokeniser, have this handle close/logout/select etc as well */
+ /* ok response from login/authenticate, then we're in happy land */
+ if ((!strcmp(ic->name, "LOGIN") || !strcmp(ic->name, "AUTHENTICATE"))
+ && ic->status->result == IMAP_OK)
+ imap->state = IMAP_ENGINE_AUTH;
+
+ if (ic->complete)
+ ic->complete(imap, ic, ic->complete_data);
+ } else {
+ camel_exception_throw(1, "got response tag unexpectedly: %s", token);
+ }
+
+ if (imap->literal != NULL) {
+ printf("Warning: continuation command '%s' finished with outstanding continuation\n", imap->literal->name);
+ ic = imap->literal;
+ /* set the command complete with a failure code? */
+ e_dlist_remove((EDListNode *)ic);
+ imap->literal = NULL;
+ }
+
+ ic = (CamelIMAPPCommand *)e_dlist_remhead(&imap->queue);
+ if (ic) {
+ printf("found outstanding op, queueing\n");
+ camel_imapp_engine_command_queue(imap, ic);
+ }
+
+ return 1;
+}
+
+
+/* Do work if there's any to do */
+int
+camel_imapp_engine_iterate(CamelIMAPPEngine *imap, CamelIMAPPCommand *icwait)
+/* throws IO,PARSE exception */
+{
+ unsigned int len;
+ unsigned char *token;
+ int tok;
+
+ if ((icwait && icwait->status != NULL) || e_dlist_empty(&imap->active))
+ return 0;
+
+ /* handle exceptions here? */
+
+ /* lock here? */
+
+ tok = camel_imapp_stream_token(imap->stream, &token, &len);
+ if (tok == '*')
+ iterate_untagged(imap);
+ else if (tok == IMAP_TOK_TOKEN)
+ iterate_completion(imap, token);
+ else if (tok == '+')
+ iterate_continuation(imap);
+ else
+ camel_exception_throw(1, "unexpected server response: %s", token);
+
+ if (e_dlist_empty(&imap->active))
+ return 0;
+
+ return 1;
+}
+
+CamelIMAPPCommand *
+camel_imapp_engine_command_new(CamelIMAPPEngine *imap, const char *name, const char *select, const char *fmt, ...)
+{
+ CamelIMAPPCommand *ic;
+ va_list ap;
+
+ ic = g_malloc0(sizeof(*ic));
+ ic->tag = imap->tag++;
+ ic->name = name;
+ ic->mem = (CamelStreamMem *)camel_stream_mem_new();
+ ic->select = g_strdup(select);
+ e_dlist_init(&ic->parts);
+
+ if (fmt && fmt[0]) {
+ va_start(ap, fmt);
+ imap_engine_command_addv(imap, ic, fmt, ap);
+ va_end(ap);
+ }
+
+ return ic;
+}
+
+void
+camel_imapp_engine_command_add(CamelIMAPPEngine *imap, CamelIMAPPCommand *ic, const char *fmt, ...)
+{
+ va_list ap;
+
+ g_assert(ic->mem); /* gets reset on queue */
+
+ if (fmt && fmt[0]) {
+ va_start(ap, fmt);
+ imap_engine_command_addv(imap, ic, fmt, ap);
+ va_end(ap);
+ }
+}
+
+void
+camel_imapp_engine_command_complete(CamelIMAPPEngine *imap, struct _CamelIMAPPCommand *ic, CamelIMAPPCommandFunc func, void *data)
+{
+ ic->complete = func;
+ ic->complete_data = data;
+}
+
+/* FIXME: make imap command's refcounted */
+void
+camel_imapp_engine_command_free (CamelIMAPPEngine *imap, CamelIMAPPCommand *ic)
+{
+ CamelIMAPPCommandPart *cp, *cn;
+
+ if (ic == NULL)
+ return;
+
+ /* validity check - we cant' free items still in any queue ... */
+ /* maybe we should just have another queue to keep them? */
+ {
+ CamelIMAPPCommand *iw;
+
+ iw = (CamelIMAPPCommand *)imap->active.head;
+ while (iw->next) {
+ if (iw == ic)
+ abort();
+ iw = iw->next;
+ }
+ iw = (CamelIMAPPCommand *)imap->queue.head;
+ while (iw->next) {
+ if (iw == ic)
+ abort();
+ iw = iw->next;
+ }
+ }
+
+ if (ic->mem)
+ camel_object_unref((CamelObject *)ic->mem);
+ imap_free_status(ic->status);
+ g_free(ic->select);
+
+ cp = (CamelIMAPPCommandPart *)ic->parts.head;
+ cn = cp->next;
+ while (cn) {
+ g_free(cp->data);
+ if (cp->ob)
+ camel_object_unref(cp->ob);
+ g_free(cp);
+ cp = cn;
+ cn = cn->next;
+ }
+
+ g_free(ic);
+}
+
+/* FIXME: error handling */
+void
+camel_imapp_engine_command_queue(CamelIMAPPEngine *imap, CamelIMAPPCommand *ic)
+{
+ CamelIMAPPCommandPart *cp;
+
+ if (ic->mem)
+ imap_engine_command_complete(imap, ic);
+
+ /* FIXME: remove select stuff */
+
+ /* see if we need to pre-queue a select command to select the right folder first */
+ if (ic->select && (imap->last_select == NULL || strcmp(ic->select, imap->last_select) != 0)) {
+ CamelIMAPPCommand *select;
+
+ /* of course ... we can't do anything like store/search if we have to select
+ first, because it'll mess up all the sequence numbers ... hrm ... bugger */
+
+ select = camel_imapp_engine_command_new(imap, "SELECT", NULL, "SELECT %s", ic->select);
+ g_free(imap->last_select);
+ imap->last_select = g_strdup(ic->select);
+ camel_imapp_engine_command_queue(imap, select);
+ /* how does it get freed? handle inside engine? */
+ }
+
+ /* first, check if command can be sent yet ... queue if not */
+ if (imap->literal != NULL) {
+ printf("%p: queueing while literal active\n", ic);
+ e_dlist_addtail(&imap->queue, (EDListNode *)ic);
+ return;
+ }
+
+ cp = (CamelIMAPPCommandPart *)ic->parts.head;
+ g_assert(cp);
+ ic->current = cp;
+
+ /* how to handle exceptions here? */
+
+ printf("queueing command \"%c%05u %s\"\n", imap->tagprefix, ic->tag, cp->data);
+ camel_stream_printf((CamelStream *)imap->stream, "%c%05u %s\r\n", imap->tagprefix, ic->tag, cp->data);
+
+ if (cp->type & CAMEL_IMAPP_COMMAND_CONTINUATION) {
+ printf("%p: active literal\n", ic);
+ g_assert(cp->next);
+ imap->literal = ic;
+ e_dlist_addtail(&imap->active, (EDListNode *)ic);
+ } else {
+ printf("%p: active non-literal\n", ic);
+ g_assert(cp->next && cp->next->next == NULL);
+ e_dlist_addtail(&imap->active, (EDListNode *)ic);
+ }
+}
+
+CamelIMAPPCommand *
+camel_imapp_engine_command_find (CamelIMAPPEngine *imap, const char *name)
+{
+ CamelIMAPPCommand *ic, *in;
+
+ ic = imap->literal;
+ if (ic && strcmp(ic->name, name) == 0)
+ return ic;
+
+ /* first, try active */
+ ic = (CamelIMAPPCommand *)imap->active.head;
+ in = ic->next;
+ while (in) {
+ if (strcmp(ic->name, name) == 0)
+ return ic;
+ ic = in;
+ in = in->next;
+ }
+
+ return NULL;
+}
+
+CamelIMAPPCommand *
+camel_imapp_engine_command_find_tag(CamelIMAPPEngine *imap, unsigned int tag)
+{
+ CamelIMAPPCommand *ic, *in;
+
+ ic = imap->literal;
+ if (ic && ic->tag == tag)
+ return ic;
+
+ ic = (CamelIMAPPCommand *)imap->active.head;
+ in = ic->next;
+ while (in) {
+ if (ic->tag == tag)
+ return ic;
+ ic = in;
+ in = in->next;
+ }
+
+ return NULL;
+}
+
+/* ********************************************************************** */
+
+CamelIMAPPSelectResponse *
+camel_imapp_engine_select(CamelIMAPPEngine *imap, const char *name)
+{
+ CamelIMAPPSelectResponse * volatile resp;
+ CamelIMAPPCommand * volatile ic = NULL;
+
+ resp = g_malloc0(sizeof(*resp));
+ imap->select_response = resp;
+
+ CAMEL_TRY {
+ ic = camel_imapp_engine_command_new(imap, "SELECT", NULL, "SELECT %s", name);
+ camel_imapp_engine_command_queue(imap, ic);
+ while (camel_imapp_engine_iterate(imap, ic) > 0)
+ ;
+
+ if (ic->status->result != IMAP_OK)
+ camel_exception_throw(1, "select failed: %s", ic->status->text);
+ resp->status = ic->status;
+ ic->status = NULL;
+ } CAMEL_CATCH (e) {
+ camel_imapp_engine_command_free(imap, ic);
+ camel_imapp_engine_select_free(imap, resp);
+ imap->select_response = NULL;
+ camel_exception_throw_ex(e);
+ } CAMEL_DONE;
+
+ camel_imapp_engine_command_free(imap, ic);
+ imap->select_response = NULL;
+
+ return resp;
+}
+
+void
+camel_imapp_engine_select_free(CamelIMAPPEngine *imap, CamelIMAPPSelectResponse *select)
+{
+ if (select) {
+ imap_free_status(select->status);
+ g_free(select);
+ }
+}
+
+/* ********************************************************************** */
+
+static void
+imap_engine_command_add_part(CamelIMAPPEngine *imap, CamelIMAPPCommand *ic, camel_imapp_command_part_t type, CamelObject *ob)
+{
+ CamelIMAPPCommandPart *cp;
+ CamelStreamNull *null;
+ unsigned int ob_size = 0;
+
+ switch(type & CAMEL_IMAPP_COMMAND_MASK) {
+ case CAMEL_IMAPP_COMMAND_DATAWRAPPER:
+ case CAMEL_IMAPP_COMMAND_STREAM:
+ null = (CamelStreamNull *)camel_stream_null_new();
+ if ( (type & CAMEL_IMAPP_COMMAND_MASK) == CAMEL_IMAPP_COMMAND_DATAWRAPPER) {
+ camel_data_wrapper_write_to_stream((CamelDataWrapper *)ob, (CamelStream *)null);
+ } else {
+ camel_stream_reset((CamelStream *)ob);
+ camel_stream_write_to_stream((CamelStream *)ob, (CamelStream *)null);
+ camel_stream_reset((CamelStream *)ob);
+ }
+ type |= CAMEL_IMAPP_COMMAND_CONTINUATION;
+ camel_object_ref(ob);
+ ob_size = null->written;
+ camel_object_unref((CamelObject *)null);
+ camel_stream_printf((CamelStream *)ic->mem, "{%u}", ob_size);
+ break;
+ case CAMEL_IMAPP_COMMAND_AUTH:
+ /* we presume we'll need to get additional data only if we're not authenticated yet */
+ camel_object_ref(ob);
+ camel_stream_printf((CamelStream *)ic->mem, "%s", ((CamelSasl *)ob)->mech);
+ if (!camel_sasl_authenticated((CamelSasl *)ob))
+ type |= CAMEL_IMAPP_COMMAND_CONTINUATION;
+ break;
+ default:
+ ob_size = 0;
+ }
+
+ cp = g_malloc0(sizeof(*cp));
+ cp->type = type;
+ cp->ob_size = ob_size;
+ cp->ob = ob;
+ cp->data_size = ic->mem->buffer->len;
+ cp->data = g_malloc(cp->data_size+1);
+ memcpy(cp->data, ic->mem->buffer->data, cp->data_size);
+ cp->data[cp->data_size] = 0;
+
+ camel_stream_reset((CamelStream *)ic->mem);
+ /* FIXME: hackish? */
+ g_byte_array_set_size(ic->mem->buffer, 0);
+
+ e_dlist_addtail(&ic->parts, (EDListNode *)cp);
+}
+
+static int len(EDList *list)
+{
+ int count = 0;
+ EDListNode *n = list->head;
+
+ while (n->next) {
+ n = n->next;
+ count++;
+ }
+ return count;
+}
+
+static void
+imap_engine_command_complete(CamelIMAPPEngine *imap, CamelIMAPPCommand *ic)
+{
+ c(printf("completing command buffer is [%d] '%.*s'\n", ic->mem->buffer->len, (int)ic->mem->buffer->len, ic->mem->buffer->data));
+ c(printf("command has %d parts\n", len(&ic->parts)));
+ if (ic->mem->buffer->len > 0)
+ imap_engine_command_add_part(imap, ic, CAMEL_IMAPP_COMMAND_SIMPLE, NULL);
+
+ c(printf("command has %d parts\n", len(&ic->parts)));
+
+ camel_object_unref((CamelObject *)ic->mem);
+ ic->mem = NULL;
+}
+
+static void
+imap_engine_command_addv(CamelIMAPPEngine *imap, CamelIMAPPCommand *ic, const char *fmt, va_list ap)
+{
+ const unsigned char *p, *ps, *start;
+ unsigned char c;
+ unsigned int width;
+ char ch;
+ int llong;
+ int left;
+ int fill;
+ int zero;
+ char *s;
+ int d;
+ long int l;
+ guint32 f;
+ CamelStream *S;
+ CamelDataWrapper *D;
+ CamelSasl *A;
+ char buffer[16];
+
+ c(printf("adding command, fmt = '%s'\n", fmt));
+
+ p = fmt;
+ ps = fmt;
+ while ( ( c = *p++ ) ) {
+ switch(c) {
+ case '%':
+ if (*p == '%') {
+ camel_stream_write((CamelStream *)ic->mem, ps, p-ps);
+ p++;
+ ps = p;
+ } else {
+ camel_stream_write((CamelStream *)ic->mem, ps, p-ps-1);
+ start = p-1;
+ width = 0;
+ left = FALSE;
+ fill = FALSE;
+ zero = FALSE;
+ llong = FALSE;
+
+ do {
+ c = *p++;
+ if (c == '0')
+ zero = TRUE;
+ else if ( c== '-')
+ left = TRUE;
+ else
+ break;
+ } while (c);
+
+ do {
+ if (isdigit(c))
+ width = width * 10 + (c-'0');
+ else
+ break;
+ } while ((c = *p++));
+
+ if (c == 'l') {
+ llong = TRUE;
+ c = *p++;
+ }
+
+ switch(c) {
+ case 'A': /* auth object - sasl auth, treat as special kind of continuation */
+ A = va_arg(ap, CamelSasl *);
+ imap_engine_command_add_part(imap, ic, CAMEL_IMAPP_COMMAND_AUTH, (CamelObject *)A);
+ break;
+ case 'S': /* stream */
+ S = va_arg(ap, CamelStream *);
+ c(printf("got stream '%p'\n", S));
+ imap_engine_command_add_part(imap, ic, CAMEL_IMAPP_COMMAND_STREAM, (CamelObject *)S);
+ break;
+ case 'D': /* datawrapper */
+ D = va_arg(ap, CamelDataWrapper *);
+ c(printf("got data wrapper '%p'\n", D));
+ imap_engine_command_add_part(imap, ic, CAMEL_IMAPP_COMMAND_DATAWRAPPER, (CamelObject *)D);
+ break;
+ case 't': /* token */
+ s = va_arg(ap, char *);
+ camel_stream_write((CamelStream *)ic->mem, s, strlen(s));
+ break;
+ case 's': /* simple string */
+ s = va_arg(ap, char *);
+ c(printf("got string '%s'\n", s));
+ /* FIXME: escpae chars, convert to literal or literal+, etc */
+ camel_stream_printf((CamelStream *)ic->mem, "\"%s\"", s);
+ break;
+ case 'f': /* imap folder name */
+ s = va_arg(ap, char *);
+ c(printf("got folder '%s'\n", s));
+ /* FIXME: encode folder name */
+ /* FIXME: namespace? */
+ camel_stream_printf((CamelStream *)ic->mem, "\"%s\"", s?s:"");
+ break;
+ case 'F': /* IMAP flags set */
+ f = va_arg(ap, guint32);
+ imap_write_flags((CamelStream *)ic->mem, f);
+ break;
+ case 'c':
+ d = va_arg(ap, int);
+ ch = d;
+ camel_stream_write((CamelStream *)ic->mem, &ch, 1);
+ break;
+ case 'd': /* int/unsigned */
+ case 'u':
+ if (llong) {
+ l = va_arg(ap, long int);
+ c(printf("got long int '%d'\n", (int)l));
+ memcpy(buffer, start, p-start);
+ buffer[p-start] = 0;
+ camel_stream_printf((CamelStream *)ic->mem, buffer, l);
+ } else {
+ d = va_arg(ap, int);
+ c(printf("got int '%d'\n", d));
+ memcpy(buffer, start, p-start);
+ buffer[p-start] = 0;
+ camel_stream_printf((CamelStream *)ic->mem, buffer, d);
+ }
+ break;
+ }
+
+ ps = p;
+ }
+ break;
+ case '\\': /* only for \\ really, we dont support \n\r etc at all */
+ c = *p;
+ if (c) {
+ g_assert(c == '\\');
+ camel_stream_write((CamelStream *)ic->mem, ps, p-ps);
+ p++;
+ ps = p;
+ }
+ }
+ }
+
+ camel_stream_write((CamelStream *)ic->mem, ps, p-ps-1);
+}
+
+
+/* here temporarily while its experimental */
+
+
+#ifdef ENABLE_THREADS
+#include <pthread.h>
+
+static pthread_key_t handler_key = 0;
+
+void camel_exception_setup(void)
+{
+ pthread_key_create(&handler_key, NULL);
+}
+
+#else
+/* this is per-thread in threaded mode */
+static struct _CamelExceptionEnv *handler = NULL;
+
+void camel_exception_setup(void)
+{
+}
+#endif
+
+void
+camel_exception_try(struct _CamelExceptionEnv *env)
+{
+#ifdef ENABLE_THREADS
+ struct _CamelExceptionEnv *handler;
+
+ handler = pthread_getspecific(handler_key);
+#endif
+ env->parent = handler;
+ handler = env;
+ env->ex = NULL;
+
+#ifdef ENABLE_THREADS
+ pthread_setspecific(handler_key, handler);
+#endif
+}
+
+void
+camel_exception_throw_ex(CamelException *ex)
+{
+ struct _CamelExceptionEnv *env;
+#ifdef ENABLE_THREADS
+ struct _CamelExceptionEnv *handler;
+
+ handler = pthread_getspecific(handler_key);
+#endif
+ printf("throwing exception '%s'\n", ex->desc);
+
+ env = handler;
+ if (env != NULL) {
+ env->ex = ex;
+ handler = env->parent;
+#ifdef ENABLE_THREADS
+ pthread_setspecific(handler_key, handler);
+#endif
+ longjmp(env->env, ex->id);
+ } else {
+ g_warning("Uncaught exception: %s\n", ex->desc);
+ /* we just crash and burn, this is a code problem */
+ /* we dont use g_assert_not_reached() since its not a noreturn function */
+ abort();
+ }
+}
+
+void
+camel_exception_throw(int id, char *fmt, ...)
+{
+ CamelException *ex;
+ va_list ap;
+
+ ex = camel_exception_new();
+ ex->id = id;
+ va_start(ap, fmt);
+ ex->desc = g_strdup_vprintf(fmt, ap);
+ va_end(ap);
+
+ camel_exception_throw_ex(ex);
+}
+
+void
+camel_exception_drop(struct _CamelExceptionEnv *env)
+{
+#ifdef ENABLE_THREADS
+ pthread_setspecific(handler_key, env->parent);
+#else
+ handler = env->parent;
+#endif
+}
+
+void
+camel_exception_done(struct _CamelExceptionEnv *env)
+{
+#ifdef ENABLE_THREADS
+ pthread_setspecific(handler_key, env->parent);
+#else
+ handler = env->parent;
+#endif
+ if (env->ex != NULL) {
+ camel_exception_free(env->ex);
+ }
+}