summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChenthill Palanisamy <pchenthill@novell.com>2009-08-20 13:41:15 +0530
committerChenthill Palanisamy <pchenthill@novell.com>2009-08-20 13:58:08 +0530
commitd7316282f8150c11f0f2a82570430221829d4eb3 (patch)
treee77e7f43653c17aa78ccb06e6c193d574b333ec6
parentd603cd4538b3a28c837a17a893c090b482a8d0c0 (diff)
downloadevolution-data-server-imap-async.tar.gz
Adding files from imapx branch required for async imap. Just makingimap-async
a initial commit to kick-start :)
-rw-r--r--camel/providers/imap/camel-imapx-server.c2818
-rw-r--r--camel/providers/imap/camel-imapx-server.h105
-rw-r--r--camel/providers/imap/camel-imapx-stream.c728
-rw-r--r--camel/providers/imap/camel-imapx-stream.h95
4 files changed, 3746 insertions, 0 deletions
diff --git a/camel/providers/imap/camel-imapx-server.c b/camel/providers/imap/camel-imapx-server.c
new file mode 100644
index 000000000..3dfe25cb2
--- /dev/null
+++ b/camel/providers/imap/camel-imapx-server.c
@@ -0,0 +1,2818 @@
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <errno.h>
+#include <string.h>
+#include <glib.h>
+
+// fixme, use own type funcs
+#include <ctype.h>
+
+#ifdef HAVE_NSS
+#include <nspr.h>
+#include <prio.h>
+#include <prerror.h>
+#include <prerr.h>
+#endif
+
+#include <camel/camel-object.h>
+#include <libedataserver/e-msgport.h>
+#include <camel/camel-url.h>
+#include <camel/camel-session.h>
+#include <camel/camel-stream-fs.h>
+#include <camel/camel-stream-null.h>
+#include <camel/camel-stream-mem.h>
+#include <camel/camel-stream-filter.h>
+#include <camel/camel-mime-filter-canon.h>
+#include <camel/camel-mime-message.h>
+#include <camel/camel-net-utils.h>
+#include <camel/camel-tcp-stream-ssl.h>
+#include <camel/camel-tcp-stream-raw.h>
+
+#include <camel/camel-sasl.h>
+#include <camel/camel-i18n.h>
+#include <camel/camel-file-utils.h>
+
+#include "camel-imapx-utils.h"
+#include "camel-imapx-exception.h"
+#include "camel-imapx-stream.h"
+#include "camel-imapx-server.h"
+#include "camel-imapx-folder.h"
+#include "camel-imapx-store.h"
+#include "camel-imapx-summary.h"
+
+#define c(x)
+#define e(x)
+
+#define CFS_CLASS(x) ((CamelFolderSummaryClass *)((CamelObject *)x)->klass)
+
+#define CIF(x) ((CamelIMAPXFolder *)x)
+
+#define QUEUE_LOCK(x) (g_mutex_lock((x)->queue_lock))
+#define QUEUE_UNLOCK(x) (g_mutex_unlock((x)->queue_lock))
+
+/* All comms with server go here */
+
+
+/* Try pipelining fetch requests, 'in bits' */
+#define MULTI_FETCH
+#define MULTI_SIZE (8196)
+
+/* How many outstanding commands do we allow before we just queue them? */
+#define MAX_COMMANDS (10)
+
+struct _uidset_state {
+ struct _CamelIMAPXEngine *ie;
+ int entries, uids;
+ int total, limit;
+ guint32 start;
+ guint32 last;
+};
+
+struct _CamelIMAPXCommand;
+void imapx_uidset_init(struct _uidset_state *ss, int total, int limit);
+int imapx_uidset_done(struct _uidset_state *ss, struct _CamelIMAPXCommand *ic);
+int imapx_uidset_add(struct _uidset_state *ss, struct _CamelIMAPXCommand *ic, const char *uid);
+
+
+typedef struct _CamelIMAPXCommandPart CamelIMAPXCommandPart;
+typedef struct _CamelIMAPXCommand CamelIMAPXCommand;
+
+typedef enum {
+ CAMEL_IMAPX_COMMAND_SIMPLE = 0,
+ CAMEL_IMAPX_COMMAND_DATAWRAPPER,
+ CAMEL_IMAPX_COMMAND_STREAM,
+ CAMEL_IMAPX_COMMAND_AUTH,
+ CAMEL_IMAPX_COMMAND_FILE,
+ CAMEL_IMAPX_COMMAND_STRING,
+ CAMEL_IMAPX_COMMAND_MASK = 0xff,
+ CAMEL_IMAPX_COMMAND_CONTINUATION = 0x8000 /* does this command expect continuation? */
+} camel_imapx_command_part_t;
+
+struct _CamelIMAPXCommandPart {
+ struct _CamelIMAPXCommandPart *next;
+ struct _CamelIMAPXCommandPart *prev;
+
+ struct _CamelIMAPXCommand *parent;
+
+ int data_size;
+ char *data;
+
+ camel_imapx_command_part_t type;
+
+ int ob_size;
+ void *ob;
+};
+
+typedef int (*CamelIMAPXEngineFunc)(struct _CamelIMAPXServer *engine, guint32 id, void *data);
+typedef void (*CamelIMAPXCommandFunc)(struct _CamelIMAPXServer *engine, struct _CamelIMAPXCommand *);
+
+struct _CamelIMAPXCommand {
+ struct _CamelIMAPXCommand *next, *prev;
+
+ char pri;
+
+ const char *name; /* command name/type (e.g. FETCH) */
+
+ char *select; /* folder to select */
+
+ struct _status_info *status; /* status for command, indicates it is complete if != NULL */
+
+ guint32 tag;
+
+ struct _CamelStreamMem *mem; /* for building the part TOOD: just use a GString? */
+ EDList parts;
+ CamelIMAPXCommandPart *current;
+
+ CamelIMAPXCommandFunc complete;
+ struct _CamelIMAPXJob *job;
+};
+
+CamelIMAPXCommand *camel_imapx_command_new(const char *name, const char *select, const char *fmt, ...);
+void camel_imapx_command_add(CamelIMAPXCommand *ic, const char *fmt, ...);
+void camel_imapx_command_free(CamelIMAPXCommand *ic);
+void camel_imapx_command_close(CamelIMAPXCommand *ic);
+
+/* states for the connection? */
+enum {
+ IMAPX_DISCONNECTED,
+ IMAPX_CONNECTED,
+ IMAPX_AUTHENTICATED,
+ IMAPX_SELECTED
+};
+
+struct _refresh_info {
+ char *uid;
+ guint32 server_flags;
+ CamelFlag *server_user_flags;
+};
+
+enum {
+ IMAPX_JOB_GET_MESSAGE,
+ IMAPX_JOB_APPEND_MESSAGE,
+ IMAPX_JOB_REFRESH_INFO,
+ IMAPX_JOB_SYNC_CHANGES,
+ IMAPX_JOB_EXPUNGE,
+ IMAPX_JOB_LIST,
+};
+
+struct _imapx_flag_change {
+ GPtrArray *infos;
+ char *name;
+};
+
+typedef struct _CamelIMAPXJob CamelIMAPXJob;
+struct _CamelIMAPXJob {
+ EMsg msg;
+
+ CamelException *ex;
+
+ void (*start)(CamelIMAPXServer *is, struct _CamelIMAPXJob *job);
+
+ // ??
+ //CamelOperation *op;
+
+ int noreply:1; /* dont wait for reply */
+ char type; /* operation type */
+ char pri; /* the command priority */
+ short commands; /* counts how many commands are outstanding */
+
+ CamelFolder *folder;
+
+ union {
+ struct {
+ /* in: uid requested */
+ char *uid;
+ /* in/out: message content stream output */
+ CamelStream *stream;
+ /* working variables */
+ size_t body_offset;
+ ssize_t body_len;
+ size_t fetch_offset;
+ } get_message;
+ struct {
+ /* array of refresh info's */
+ GArray *infos;
+ /* used for biulding uidset stuff */
+ int index;
+ int last_index;
+ struct _uidset_state uidset;
+ /* changes during refresh */
+ CamelChangeInfo *changes;
+ } refresh_info;
+ struct {
+ GPtrArray *infos;
+ guint32 on_set;
+ guint32 off_set;
+ GArray *on_user; /* imapx_flag_change */
+ GArray *off_user;
+ } sync_changes;
+ struct {
+ char *path;
+ CamelMessageInfo *info;
+ } append_message;
+ struct {
+ char *pattern;
+ guint32 flags;
+ GHashTable *folders;
+ } list;
+ } u;
+};
+
+enum {
+ USE_SSL_NEVER,
+ USE_SSL_ALWAYS,
+ USE_SSL_WHEN_POSSIBLE
+};
+
+#define SSL_PORT_FLAGS (CAMEL_TCP_STREAM_SSL_ENABLE_SSL2 | CAMEL_TCP_STREAM_SSL_ENABLE_SSL3)
+#define STARTTLS_FLAGS (CAMEL_TCP_STREAM_SSL_ENABLE_TLS)
+
+
+
+static void imapx_select(CamelIMAPXServer *is, CamelFolder *folder);
+
+
+
+/*
+ this creates a uid (or sequence number) set directly into a command,
+ if total is set, then we break it up into total uids. (i.e. command time)
+ if limit is set, then we break it up into limit entries (i.e. command length)
+*/
+void
+imapx_uidset_init(struct _uidset_state *ss, int total, int limit)
+{
+ ss->uids = 0;
+ ss->entries = 0;
+ ss->start = 0;
+ ss->last = 0;
+ ss->total = total;
+ ss->limit = limit;
+}
+
+int
+imapx_uidset_done(struct _uidset_state *ss, CamelIMAPXCommand *ic)
+{
+ int ret = 0;
+
+ if (ss->last != 0 && ss->last != ss->start) {
+ camel_imapx_command_add(ic, ":%d", ss->last);
+ }
+
+ ret = ss->last != 0;
+
+ ss->start = 0;
+ ss->last = 0;
+ ss->uids = 0;
+ ss->entries = 0;
+
+ return ret;
+}
+
+int
+imapx_uidset_add(struct _uidset_state *ss, CamelIMAPXCommand *ic, const char *uid)
+{
+ guint32 uidn;
+
+ uidn = strtoul(uid, NULL, 10);
+ if (uidn == 0)
+ return -1;
+
+ ss->uids++;
+
+ printf("uidset add '%s'\n", uid);
+
+ if (ss->last == 0) {
+ printf(" start\n");
+ camel_imapx_command_add(ic, "%d", uidn);
+ ss->entries++;
+ ss->start = uidn;
+ } else {
+ if (ss->last != uidn-1) {
+ if (ss->last == ss->start) {
+ printf(" ,next\n");
+ camel_imapx_command_add(ic, ",%d", uidn);
+ ss->entries++;
+ } else {
+ printf(" :range\n");
+ camel_imapx_command_add(ic, ":%d,%d", ss->last, uidn);
+ ss->entries+=2;
+ }
+ ss->start = uidn;
+ }
+ }
+
+ ss->last = uidn;
+
+ if ((ss->limit && ss->entries >= ss->limit)
+ || (ss->total && ss->uids >= ss->total)) {
+ printf(" done, %d entries, %d uids\n", ss->entries, ss->uids);
+ imapx_uidset_done(ss, ic);
+ return 1;
+ }
+
+ return 0;
+}
+
+
+
+
+
+
+static void
+imapx_command_add_part(CamelIMAPXCommand *ic, camel_imapx_command_part_t type, void *o)
+{
+ CamelIMAPXCommandPart *cp;
+ CamelStreamNull *null;
+ unsigned int ob_size = 0;
+
+ /* TODO: literal+? */
+
+ switch(type & CAMEL_IMAPX_COMMAND_MASK) {
+ case CAMEL_IMAPX_COMMAND_DATAWRAPPER:
+ case CAMEL_IMAPX_COMMAND_STREAM: {
+ CamelObject *ob = o;
+
+ /* TODO: seekable streams we could just seek to the end and back */
+ null = (CamelStreamNull *)camel_stream_null_new();
+ if ( (type & CAMEL_IMAPX_COMMAND_MASK) == CAMEL_IMAPX_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_IMAPX_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_IMAPX_COMMAND_AUTH: {
+ CamelObject *ob = o;
+
+ /* 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_IMAPX_COMMAND_CONTINUATION;
+ break;
+ }
+ case CAMEL_IMAPX_COMMAND_FILE: {
+ char *path = o;
+ struct stat st;
+
+ if (stat(path, &st) == 0) {
+ o = g_strdup(o);
+ ob_size = st.st_size;
+ } else
+ o = NULL;
+
+ camel_stream_printf((CamelStream *)ic->mem, "{%u}", ob_size);
+ type |= CAMEL_IMAPX_COMMAND_CONTINUATION;
+ break;
+ }
+ case CAMEL_IMAPX_COMMAND_STRING:
+ o = g_strdup(o);
+ ob_size = strlen(o);
+ camel_stream_printf((CamelStream *)ic->mem, "{%u}", ob_size);
+ type |= CAMEL_IMAPX_COMMAND_CONTINUATION;
+ break;
+ default:
+ ob_size = 0;
+ }
+
+ cp = g_malloc0(sizeof(*cp));
+ cp->type = type;
+ cp->ob_size = ob_size;
+ cp->ob = o;
+ 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 void
+imapx_command_addv(CamelIMAPXCommand *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;
+ char *P;
+ int d;
+ long int l;
+ guint32 f;
+ CamelFlag *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 {
+ // FIXME: ascii isdigit
+ 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 *);
+ imapx_command_add_part(ic, CAMEL_IMAPX_COMMAND_AUTH, A);
+ break;
+ case 'S': /* stream */
+ S = va_arg(ap, CamelStream *);
+ c(printf("got stream '%p'\n", S));
+ imapx_command_add_part(ic, CAMEL_IMAPX_COMMAND_STREAM, S);
+ break;
+ case 'D': /* datawrapper */
+ D = va_arg(ap, CamelDataWrapper *);
+ c(printf("got data wrapper '%p'\n", D));
+ imapx_command_add_part(ic, CAMEL_IMAPX_COMMAND_DATAWRAPPER, D);
+ break;
+ case 'P': /* filename path */
+ P = va_arg(ap, char *);
+ c(printf("got file path '%s'\n", P));
+ imapx_command_add_part(ic, CAMEL_IMAPX_COMMAND_FILE, P);
+ 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));
+ if (*s) {
+ unsigned char mask = imapx_is_mask(s);
+
+ if (mask & IMAPX_TYPE_ATOM_CHAR)
+ camel_stream_write((CamelStream *)ic->mem, s, strlen(s));
+ else if (mask & IMAPX_TYPE_TEXT_CHAR) {
+ camel_stream_write((CamelStream *)ic->mem, "\"", 1);
+ while (*s) {
+ char *start = s;
+
+ while (*s && imapx_is_quoted_char(*s))
+ s++;
+ camel_stream_write((CamelStream *)ic->mem, start, s-start);
+ if (*s) {
+ camel_stream_write((CamelStream *)ic->mem, "\\", 1);
+ camel_stream_write((CamelStream *)ic->mem, s, 1);
+ s++;
+ }
+ }
+ camel_stream_write((CamelStream *)ic->mem, "\"", 1);
+ } else {
+ imapx_command_add_part(ic, CAMEL_IMAPX_COMMAND_STRING, s);
+ }
+ } else {
+ camel_stream_write((CamelStream *)ic->mem, "\"\"", 2);
+ }
+ 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);
+ F = va_arg(ap, CamelFlag *);
+ imap_write_flags((CamelStream *)ic->mem, f, 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);
+}
+
+
+
+
+
+
+
+
+
+
+
+
+CamelIMAPXCommand *
+camel_imapx_command_new(const char *name, const char *select, const char *fmt, ...)
+{
+ CamelIMAPXCommand *ic;
+ static int tag = 0;
+ va_list ap;
+
+ ic = g_malloc0(sizeof(*ic));
+ ic->tag = 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);
+ imapx_command_addv(ic, fmt, ap);
+ va_end(ap);
+ }
+
+ return ic;
+}
+
+void
+camel_imapx_command_add(CamelIMAPXCommand *ic, const char *fmt, ...)
+{
+ va_list ap;
+
+ g_assert(ic->mem); /* gets reset on queue */
+
+ if (fmt && fmt[0]) {
+ va_start(ap, fmt);
+ imapx_command_addv(ic, fmt, ap);
+ va_end(ap);
+ }
+}
+
+void
+camel_imapx_command_free(CamelIMAPXCommand *ic)
+{
+ CamelIMAPXCommandPart *cp;
+
+ if (ic == NULL)
+ return;
+
+ if (ic->mem)
+ camel_object_unref((CamelObject *)ic->mem);
+ imap_free_status(ic->status);
+ g_free(ic->select);
+
+ while ( (cp = ((CamelIMAPXCommandPart *)e_dlist_remhead(&ic->parts))) ) {
+ g_free(cp->data);
+ if (cp->ob) {
+ switch (cp->type & CAMEL_IMAPX_COMMAND_MASK) {
+ case CAMEL_IMAPX_COMMAND_FILE:
+ case CAMEL_IMAPX_COMMAND_STRING:
+ g_free(cp->ob);
+ break;
+ default:
+ camel_object_unref(cp->ob);
+ }
+ }
+ g_free(cp);
+ }
+
+ g_free(ic);
+}
+
+void
+camel_imapx_command_close(CamelIMAPXCommand *ic)
+{
+ if (ic->mem) {
+ c(printf("completing command buffer is [%d] '%.*s'\n", ic->mem->buffer->len, (int)ic->mem->buffer->len, ic->mem->buffer->data));
+ if (ic->mem->buffer->len > 0)
+ imapx_command_add_part(ic, CAMEL_IMAPX_COMMAND_SIMPLE, NULL);
+
+ camel_object_unref((CamelObject *)ic->mem);
+ ic->mem = NULL;
+ }
+}
+
+/* FIXME: error handling */
+#if 0
+void
+camel_imapx_engine_command_queue(CamelIMAPXEngine *imap, CamelIMAPXCommand *ic)
+{
+ CamelIMAPXCommandPart *cp;
+
+ if (ic->mem) {
+ c(printf("completing command buffer is [%d] '%.*s'\n", ic->mem->buffer->len, (int)ic->mem->buffer->len, ic->mem->buffer->data));
+ if (ic->mem->buffer->len > 0)
+ imapx_command_add_part(imap, ic, CAMEL_IMAPX_COMMAND_SIMPLE, NULL);
+
+ camel_object_unref((CamelObject *)ic->mem);
+ ic->mem = NULL;
+ }
+
+ /* now have completed command? */
+}
+#endif
+
+/* Get a path into the cache, works like maildir, but isn't */
+static char *
+imapx_get_path_uid(CamelIMAPXServer *is, CamelFolder *folder, const char *bit, const char *uid)
+{
+ char *dir, *path;
+
+ // big fixme of course, we need to create the path if it doesn't exist,
+ // base it on the server, blah blah
+ if (bit == NULL)
+ bit = strchr(uid, '-') == NULL?"cur":"new";
+ dir = g_strdup_printf("/tmp/imap-cache/%s/%s", folder->full_name, bit);
+
+ camel_mkdir(dir, 0777);
+ path = g_strdup_printf("%s/%s", dir, uid);
+ g_free(dir);
+
+ return path;
+}
+
+/* Must hold QUEUE_LOCK */
+static void
+imapx_command_start(CamelIMAPXServer *imap, CamelIMAPXCommand *ic)
+{
+ CamelIMAPXCommandPart *cp;
+
+ camel_imapx_command_close(ic);
+
+ /* FIXME: assert the selected folder == ic->selected */
+
+ cp = (CamelIMAPXCommandPart *)ic->parts.head;
+ g_assert(cp->next);
+
+ ic->current = cp;
+
+ /* TODO: If we support literal+ we should be able to write the whole command out
+ at this point .... >here< */
+
+ if (cp->type & CAMEL_IMAPX_COMMAND_CONTINUATION)
+ imap->literal = ic;
+
+ e_dlist_addtail(&imap->active, (EDListNode *)ic);
+
+ printf("Staring command (active=%d,%s) %c%05u %s\r\n", e_dlist_length(&imap->active), imap->literal?" literal":"", imap->tagprefix, ic->tag, cp->data);
+ camel_stream_printf((CamelStream *)imap->stream, "%c%05u %s\r\n", imap->tagprefix, ic->tag, cp->data);
+}
+
+/* must have QUEUE lock */
+static void
+imapx_command_start_next(CamelIMAPXServer *imap)
+{
+ CamelIMAPXCommand *ic, *nc;
+ int count = 0;
+ int pri = -128;
+
+ /* See if we can start another task yet.
+
+ If we're waiting for a literal, we cannot proceed.
+
+ If we're about to change the folder we're
+ looking at from user-direction, we dont proceed.
+
+ If we have a folder selected, first see if any
+ jobs are waiting on it, but only if they are
+ at least as high priority as anything we
+ have running.
+
+ If we dont, select the first folder required,
+ then queue all the outstanding jobs on it, that
+ are at least as high priority as the first.
+
+ This is very shitty code!
+ */
+
+ printf("** Starting next command\n");
+
+ if (imap->literal != NULL || imap->select_pending != NULL) {
+ printf("* no, waiting for literal/pending select '%s'\n", imap->select_pending->full_name);
+ return;
+ }
+
+ ic = (CamelIMAPXCommand *)imap->queue.head;
+ nc = ic->next;
+ if (nc == NULL) {
+ printf("* no, no jobs\n");
+ return;
+ }
+
+ /* See if any queued jobs on this select first */
+ if (imap->select) {
+ printf("- we're selected on '%s', current jobs?\n", imap->select);
+ for (ic = (CamelIMAPXCommand *)imap->active.head;ic->next;ic=ic->next) {
+ printf("- %3d '%s'\n", (int)ic->pri, ic->name);
+ if (ic->pri > pri)
+ pri = ic->pri;
+ count++;
+ if (count > MAX_COMMANDS) {
+ printf("** too many jobs busy, waiting for results for now\n");
+ return;
+ }
+ }
+
+ printf("-- Checking job queue\n");
+ count = 0;
+ ic = (CamelIMAPXCommand *)imap->queue.head;
+ nc = ic->next;
+ while (nc && imap->literal == NULL && count < MAX_COMMANDS && ic->pri >= pri) {
+ printf("-- %3d '%s'?\n", (int)ic->pri, ic->name);
+ if (ic->select == NULL || strcmp(ic->select, imap->select) == 0) {
+ printf("--> starting '%s'\n", ic->name);
+ pri = ic->pri;
+ e_dlist_remove((EDListNode *)ic);
+ imapx_command_start(imap, ic);
+ count++;
+ }
+ ic = nc;
+ nc = nc->next;
+ }
+
+ if (count)
+ return;
+
+ ic = (CamelIMAPXCommand *)imap->queue.head;
+ }
+
+ /* If we need to select a folder for the first command, do it now, once
+ it is complete it will re-call us if it succeeded */
+ if (ic->job->folder) {
+ imapx_select(imap, ic->job->folder);
+ } else {
+ pri = ic->pri;
+ nc = ic->next;
+ count = 0;
+ while (nc && imap->literal == NULL && count < MAX_COMMANDS && ic->pri >= pri) {
+ if (ic->select == NULL || (imap->select && strcmp(ic->select, imap->select))) {
+ printf("* queueing job %3d '%s'\n", (int)ic->pri, ic->name);
+ pri = ic->pri;
+ e_dlist_remove((EDListNode *)ic);
+ imapx_command_start(imap, ic);
+ count++;
+ }
+ ic = nc;
+ nc = nc->next;
+ }
+ }
+}
+
+static void
+imapx_command_queue(CamelIMAPXServer *imap, CamelIMAPXCommand *ic)
+{
+ CamelIMAPXCommand *scan;
+
+ /* We enqueue in priority order, new messages have
+ higher priority than older messages with the same priority */
+
+ camel_imapx_command_close(ic);
+
+ printf("enqueue job '%.*s'\n", ((CamelIMAPXCommandPart *)ic->parts.head)->data_size, ((CamelIMAPXCommandPart *)ic->parts.head)->data);
+
+ QUEUE_LOCK(imap);
+
+ scan = (CamelIMAPXCommand *)imap->queue.head;
+ if (scan->next == NULL)
+ e_dlist_addtail(&imap->queue, (EDListNode *)ic);
+ else {
+ while (scan->next) {
+ if (ic->pri >= scan->pri)
+ break;
+ scan = scan->next;
+ }
+
+ scan->prev->next = ic;
+ ic->next = scan;
+ ic->prev = scan->prev;
+ scan->prev = ic;
+ }
+
+ imapx_command_start_next(imap);
+
+ QUEUE_UNLOCK(imap);
+}
+
+/* Must have QUEUE lock */
+static CamelIMAPXCommand *
+imapx_find_command_tag(CamelIMAPXServer *imap, unsigned int tag)
+{
+ CamelIMAPXCommand *ic;
+
+ ic = imap->literal;
+ if (ic && ic->tag == tag)
+ return ic;
+
+ for (ic = (CamelIMAPXCommand *)imap->active.head;ic->next;ic=ic->next)
+ if (ic->tag == tag)
+ return ic;
+
+ return NULL;
+}
+
+/* Must not have QUEUE lock */
+static CamelIMAPXJob *
+imapx_find_job(CamelIMAPXServer *imap, int type, const char *uid)
+{
+ CamelIMAPXJob *job;
+
+ QUEUE_LOCK(imap);
+
+ for (job = (CamelIMAPXJob *)imap->jobs.head;job->msg.ln.next;job = (CamelIMAPXJob *)job->msg.ln.next) {
+ if (job->type != type)
+ continue;
+
+ switch (type) {
+ case IMAPX_JOB_GET_MESSAGE:
+ if (imap->select
+ && strcmp(job->folder->full_name, imap->select) == 0
+ && strcmp(job->u.get_message.uid, uid) == 0)
+ goto found;
+ break;
+ case IMAPX_JOB_REFRESH_INFO:
+ if (imap->select
+ && strcmp(job->folder->full_name, imap->select) == 0)
+ goto found;
+ break;
+ case IMAPX_JOB_LIST:
+ goto found;
+ }
+ }
+
+ job = NULL;
+found:
+
+ QUEUE_UNLOCK(imap);
+
+ return job;
+}
+
+/* Process all expunged results we had from the last command.
+ This can be somewhat slow ... */
+static void
+imapx_expunged(CamelIMAPXServer *imap)
+{
+ int count = 1, index=0, expunge;
+ const CamelMessageInfo *iterinfo;
+ CamelIterator *iter;
+
+ g_assert(imap->select_folder);
+
+ if (imap->expunged->len == 0)
+ return;
+
+ printf("Processing '%d' expunges\n", imap->expunged->len);
+
+ expunge = g_array_index(imap->expunged, guint32, index++);
+ iter = camel_folder_summary_search(imap->select_folder->summary, NULL, NULL, NULL, NULL);
+ while ((iterinfo = camel_iterator_next(iter, NULL))) {
+ if (count == expunge) {
+ printf("expunging '%d' - '%s'\n", expunge, camel_message_info_subject(iterinfo));
+ camel_folder_summary_remove(imap->select_folder->summary, (CamelMessageInfo *)iterinfo);
+ if (index >= imap->expunged->len)
+ break;
+ expunge = g_array_index(imap->expunged, guint32, index++);
+ } else
+ //FIXME: skip over offline uids
+ count++;
+ }
+ camel_iterator_free(iter);
+ g_array_set_size(imap->expunged, 0);
+}
+
+/* handle any untagged responses */
+static int
+imapx_untagged(CamelIMAPXServer *imap)
+{
+ unsigned int id, len;
+ unsigned char *token, *p, c;
+ int tok;
+ struct _status_info *sinfo;
+
+ e(printf("got untagged response\n"));
+ id = 0;
+ tok = camel_imapx_stream_token(imap->stream, &token, &len);
+ if (tok == IMAP_TOK_INT) {
+ id = strtoul(token, NULL, 10);
+ tok = camel_imapx_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);
+
+ switch (imap_tokenise(token, len)) {
+ case IMAP_CAPABILITY:
+ if (imap->cinfo)
+ imap_free_capability(imap->cinfo);
+ imap->cinfo = imap_parse_capability(imap->stream);
+ printf("got capability flags %08x\n", imap->cinfo->capa);
+ return 0;
+ case IMAP_EXPUNGE: {
+ guint32 expunge = id;
+
+ printf("expunged: %d\n", id);
+ g_array_append_val(imap->expunged, expunge);
+ break;
+ }
+ case IMAP_EXISTS:
+ printf("exists: %d\n", id);
+ imap->exists = id;
+ break;
+ case IMAP_FLAGS: {
+ guint32 flags;
+
+ imap_parse_flags(imap->stream, &flags, NULL);
+
+ printf("flags: %08x\n", flags);
+ break;
+ }
+ case IMAP_FETCH: {
+ struct _fetch_info *finfo;
+
+ finfo = imap_parse_fetch(imap->stream);
+
+ //imap_dump_fetch(finfo);
+
+ if ((finfo->got & (FETCH_BODY|FETCH_UID)) == (FETCH_BODY|FETCH_UID)) {
+ CamelIMAPXJob *job = imapx_find_job(imap, IMAPX_JOB_GET_MESSAGE, finfo->uid);
+
+ /* This must've been a get-message request, fill out the body stream,
+ in the right spot */
+
+ if (job) {
+#ifdef MULTI_FETCH
+ job->u.get_message.body_offset = finfo->offset;
+ camel_seekable_stream_seek((CamelSeekableStream *)job->u.get_message.stream, finfo->offset, CAMEL_STREAM_SET);
+#endif
+ job->u.get_message.body_len = camel_stream_write_to_stream(finfo->body, job->u.get_message.stream);
+ if (job->u.get_message.body_len == -1) {
+ camel_exception_setv(job->ex, 1, "error writing to cache stream: %s\n", g_strerror(errno));
+ camel_object_unref(job->u.get_message.stream);
+ job->u.get_message.stream = NULL;
+ }
+ }
+ }
+
+ if ((finfo->got & (FETCH_FLAGS|FETCH_UID)) == (FETCH_FLAGS|FETCH_UID)) {
+ CamelIMAPXJob *job = imapx_find_job(imap, IMAPX_JOB_REFRESH_INFO, NULL);
+
+ /* This is either a refresh_info job, check to see if it is and update
+ if so, otherwise it must've been an unsolicited response, so update
+ the summary to match */
+
+ if (job) {
+ struct _refresh_info r;
+
+ r.uid = finfo->uid;
+ finfo->uid = NULL;
+ r.server_flags = finfo->flags;
+ r.server_user_flags = finfo->user_flags;
+ finfo->user_flags = NULL;
+ g_array_append_val(job->u.refresh_info.infos, r);
+ } else {
+ printf("Unsolicited flags response '%s' %08x\n", finfo->uid, finfo->flags);
+ // TODO, we need the folder as well as the name in the select field.
+ }
+ }
+
+ if ((finfo->got & (FETCH_HEADER|FETCH_UID)) == (FETCH_HEADER|FETCH_UID)) {
+ CamelIMAPXJob *job = imapx_find_job(imap, IMAPX_JOB_REFRESH_INFO, NULL);
+
+ /* This must be a refresh info job as well, but it has asked for
+ new messages to be added to the index */
+
+ if (job) {
+ CamelMimeParser *mp;
+ CamelMessageInfo *mi;
+
+ /* Do we want to save these headers for later too? Do we care? */
+
+ mp = camel_mime_parser_new();
+ camel_mime_parser_init_with_stream(mp, finfo->header);
+ mi = camel_message_info_new_from_parser(job->folder->summary, mp);
+ camel_object_unref(mp);
+
+ if (mi) {
+ GArray *infos = job->u.refresh_info.infos;
+ int i = job->u.refresh_info.last_index;
+
+ /* This is rather inefficent, but should be ok if we're expecting it
+ since we break each fetch into lots of 100 */
+ mi->uid = g_strdup(finfo->uid);
+ for (i=0;i<infos->len;i++) {
+ struct _refresh_info *r = &g_array_index(infos, struct _refresh_info, i);
+
+ if (r->uid && !strcmp(r->uid, finfo->uid)) {
+ ((CamelMessageInfoBase *)mi)->flags = r->server_flags;
+ ((CamelIMAPXMessageInfo *)mi)->server_flags = r->server_flags;
+ camel_flag_list_copy(&((CamelMessageInfoBase *)mi)->user_flags, &r->server_user_flags);
+ ((CamelIMAPXMessageInfo *)mi)->server_user_flags = r->server_user_flags;
+ break;
+ }
+ }
+ camel_folder_summary_add(job->folder->summary, mi);
+ }
+ }
+ }
+
+ imap_free_fetch(finfo);
+ break;
+ }
+ case IMAP_LIST: case IMAP_LSUB: {
+ struct _list_info *linfo = imap_parse_list(imap->stream);
+ CamelIMAPXJob *job = imapx_find_job(imap, IMAPX_JOB_LIST, linfo->name);
+
+ // TODO: we want to make sure the names match?
+
+ printf("list: '%s' (%c)\n", linfo->name, linfo->separator);
+ if (job && g_hash_table_lookup(job->u.list.folders, linfo->name) == NULL) {
+ g_hash_table_insert(job->u.list.folders, linfo->name, linfo);
+ } else {
+ g_warning("got list response but no current listing job happening?\n");
+ imap_free_list(linfo);
+ }
+ break;
+ }
+ case IMAP_RECENT:
+ printf("recent: %d\n", id);
+ imap->recent = id;
+ break;
+ 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 */
+ camel_imapx_stream_ungettoken(imap->stream, tok, token, len);
+ sinfo = imap_parse_status(imap->stream);
+ camel_object_trigger_event(imap, "status", sinfo);
+ switch(sinfo->condition) {
+ case IMAP_READ_WRITE:
+ imap->mode = IMAPX_MODE_READ|IMAPX_MODE_WRITE;
+ printf("folder is read-write\n");
+ break;
+ case IMAP_READ_ONLY:
+ imap->mode = IMAPX_MODE_READ;
+ printf("folder is read-only\n");
+ break;
+ case IMAP_UIDVALIDITY:
+ imap->uidvalidity = sinfo->u.uidvalidity;
+ break;
+ case IMAP_UNSEEN:
+ imap->unseen = sinfo->u.unseen;
+ break;
+ case IMAP_PERMANENTFLAGS:
+ imap->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;
+ }
+ imap_free_status(sinfo);
+ return 0;
+ default:
+ /* unknown response, just ignore it */
+ printf("unknown token: %s\n", token);
+ }
+
+ return camel_imapx_stream_skip(imap->stream);
+}
+
+/* handle any continuation requests
+ either data continuations, or auth continuation */
+static int
+imapx_continuation(CamelIMAPXServer *imap)
+{
+ CamelIMAPXCommand *ic, *newliteral = NULL;
+ CamelIMAPXCommandPart *cp;
+
+ printf("got continuation response\n");
+
+ /* The 'literal' pointer is like a write-lock, nothing else
+ can write while we have it ... so we dont need any
+ ohter lock here. All other writes go through
+ queue-lock */
+
+ ic = imap->literal;
+ if (ic == NULL) {
+ camel_imapx_stream_skip(imap->stream);
+ 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_IMAPX_COMMAND_MASK) {
+ case CAMEL_IMAPX_COMMAND_DATAWRAPPER:
+ printf("writing data wrapper to literal\n");
+ camel_data_wrapper_write_to_stream((CamelDataWrapper *)cp->ob, (CamelStream *)imap->stream);
+ break;
+ case CAMEL_IMAPX_COMMAND_STREAM:
+ printf("writing stream to literal\n");
+ camel_stream_write_to_stream((CamelStream *)cp->ob, (CamelStream *)imap->stream);
+ break;
+ case CAMEL_IMAPX_COMMAND_AUTH: {
+ CamelException *ex = camel_exception_new();
+ char *resp;
+ unsigned char *token;
+ int tok, len;
+
+ tok = camel_imapx_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 */
+ newliteral = ic;
+
+ break; }
+ case CAMEL_IMAPX_COMMAND_FILE: {
+ CamelStream *file;
+
+ printf("writing file '%s' to literal\n", (char *)cp->ob);
+
+ // FIXME: errors
+ if (cp->ob && (file = camel_stream_fs_new_with_name(cp->ob, O_RDONLY, 0))) {
+ camel_stream_write_to_stream(file, (CamelStream *)imap->stream);
+ camel_object_unref(file);
+ } else if (cp->ob_size > 0) {
+ // Server is expecting data ... ummm, send it zeros? abort?
+ }
+ break; }
+ case CAMEL_IMAPX_COMMAND_STRING:
+ camel_stream_write((CamelStream *)imap->stream, cp->ob, cp->ob_size);
+ break;
+ default:
+ /* should we just ignore? */
+ imap->literal = NULL;
+ camel_exception_throw(1, "continuation response for non-continuation request");
+ }
+
+ camel_imapx_stream_skip(imap->stream);
+
+ 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_IMAPX_COMMAND_CONTINUATION) {
+ newliteral = ic;
+ } else {
+ g_assert(cp->next->next == NULL);
+ }
+ } else {
+ printf("%p: queueing continuation\n", ic);
+ camel_stream_printf((CamelStream *)imap->stream, "\r\n");
+ }
+
+ QUEUE_LOCK(imap);
+ imap->literal = newliteral;
+
+ imapx_command_start_next(imap);
+ QUEUE_UNLOCK(imap);
+
+ return 1;
+}
+
+/* handle a completion line */
+static int
+imapx_completion(CamelIMAPXServer *imap, unsigned char *token, int len)
+{
+ CamelIMAPXCommand * volatile 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);
+
+ QUEUE_LOCK(imap);
+ if ((ic = imapx_find_command_tag(imap, tag)) == NULL) {
+ QUEUE_UNLOCK(imap);
+ camel_exception_throw(1, "got response tag unexpectedly: %s", token);
+ }
+
+ printf("Got completion response for command %05u '%s'\n", ic->tag, ic->name);
+
+ e_dlist_remove((EDListNode *)ic);
+ e_dlist_addtail(&imap->done, (EDListNode *)ic);
+ if (imap->literal == ic)
+ imap->literal = NULL;
+
+ if (ic->current->next->next) {
+ QUEUE_UNLOCK(imap);
+ camel_exception_throw(1, "command still has unsent parts?", ic->name);
+ }
+
+ /* A follow-on command might've already queued a new literal since were were done with ours? */
+// if (imap->literal != NULL) {
+// QUEUE_UNLOCK(imap);
+// camel_exception_throw(1, "command still has outstanding continuation", imap->literal->name);
+// }
+
+ QUEUE_UNLOCK(imap);
+ ic->status = imap_parse_status(imap->stream);
+
+ if (ic->complete)
+ ic->complete(imap, ic);
+
+ if (imap->expunged->len)
+ imapx_expunged(imap);
+
+ QUEUE_LOCK(imap);
+ if (ic->complete) {
+ e_dlist_remove((EDListNode *)ic);
+ camel_imapx_command_free(ic);
+ }
+ imapx_command_start_next(imap);
+ QUEUE_UNLOCK(imap);
+
+ return 1;
+}
+
+static void
+imapx_step(CamelIMAPXServer *is)
+/* throws IO,PARSE exception */
+{
+ unsigned int len;
+ unsigned char *token;
+ int tok;
+
+ // poll ? wait for other stuff? loop?
+ tok = camel_imapx_stream_token(is->stream, &token, &len);
+ if (tok == '*')
+ imapx_untagged(is);
+ else if (tok == IMAP_TOK_TOKEN)
+ imapx_completion(is, token, len);
+ else if (tok == '+')
+ imapx_continuation(is);
+ else
+ camel_exception_throw(1, "unexpected server response: %s", token);
+}
+
+/* Used to run 1 command synchronously,
+ use for capa, login, and selecting only. */
+static void
+imapx_command_run(CamelIMAPXServer *is, CamelIMAPXCommand *ic)
+/* throws IO,PARSE exception */
+{
+ camel_imapx_command_close(ic);
+ QUEUE_LOCK(is);
+ g_assert(e_dlist_empty(&is->active));
+ imapx_command_start(is, ic);
+ QUEUE_UNLOCK(is);
+ do {
+ imapx_step(is);
+ } while (ic->status == NULL);
+
+ e_dlist_remove((EDListNode *)ic);
+}
+
+/* ********************************************************************** */
+static void
+imapx_select_done(CamelIMAPXServer *is, CamelIMAPXCommand *ic)
+{
+
+ if (ic->status->result != IMAP_OK) {
+ EDList failed = E_DLIST_INITIALISER(failed);
+ CamelIMAPXCommand *cw, *cn;
+
+ printf("Select failed\n");
+
+ QUEUE_LOCK(is);
+ cw = (CamelIMAPXCommand *)is->queue.head;
+ cn = cw->next;
+ while (cn) {
+ if (cw->select && strcmp(cw->select, is->select_pending->full_name) == 0) {
+ e_dlist_remove((EDListNode *)cw);
+ e_dlist_addtail(&failed, (EDListNode *)cw);
+ }
+ cw = cn;
+ cn = cn->next;
+ }
+ QUEUE_UNLOCK(is);
+
+ cw = (CamelIMAPXCommand *)failed.head;
+ cn = cw->next;
+ while (cn) {
+ cw->status = imap_copy_status(ic->status);
+ cw->complete(is, cw);
+ camel_imapx_command_free(cw);
+ cw = cn;
+ cn = cn->next;
+ }
+
+ camel_object_unref(is->select_pending);
+ } else {
+ printf("Select ok!\n");
+
+ is->select_folder = is->select_pending;
+ is->select = g_strdup(is->select_folder->full_name);
+ is->state = IMAPX_SELECTED;
+#if 0
+ /* This must trigger a complete index rebuild! */
+ if (is->uidvalidity && is->uidvalidity != ((CamelIMAPXSummary *)is->select_folder->summary)->uidvalidity)
+ g_warning("uidvalidity doesn't match!");
+
+ /* This should trigger a new messages scan */
+ if (is->exists != is->select_folder->summary->root_view->total_count)
+ g_warning("exists is %d our summary is %d and summary exists is %d\n", is->exists,
+ is->select_folder->summary->root_view->total_count,
+ ((CamelIMAPXSummary *)is->select_folder->summary)->exists);
+#endif
+ }
+
+ is->select_pending = NULL;
+}
+
+static void
+imapx_select(CamelIMAPXServer *is, CamelFolder *folder)
+{
+ CamelIMAPXCommand *ic;
+
+ /* Select is complicated by the fact we may have commands
+ active on the server for a different selection.
+
+ So this waits for any commands to complete, selects the
+ new folder, and halts the queuing of any new commands.
+ It is assumed whomever called is us about to issue
+ a high-priority command anyway */
+
+ /* TODO check locking here, pending_select will do
+ most of the work for normal commands, but not
+ for another select */
+
+ if (is->select_pending)
+ return;
+
+ if (is->select && strcmp(is->select, folder->full_name) == 0)
+ return;
+
+ is->select_pending = folder;
+ camel_object_ref(folder);
+ if (is->select_folder) {
+ while (!e_dlist_empty(&is->active)) {
+ QUEUE_UNLOCK(is);
+ imapx_step(is);
+ QUEUE_LOCK(is);
+ }
+ g_free(is->select);
+ camel_object_unref(is->select_folder);
+ is->select = NULL;
+ is->select_folder = NULL;
+ }
+
+ is->uidvalidity = 0;
+ is->unseen = 0;
+ is->permanentflags = 0;
+ is->exists = 0;
+ is->recent = 0;
+ is->mode = 0;
+
+ /* Hrm, what about reconnecting? */
+ is->state = IMAPX_AUTHENTICATED;
+
+ ic = camel_imapx_command_new("SELECT", NULL, "SELECT %s", CIF(folder)->raw_name);
+ ic->complete = imapx_select_done;
+ imapx_command_start(is, ic);
+}
+
+static void
+imapx_connect(CamelIMAPXServer *is, int ssl_mode, int try_starttls)
+/* throws IO exception */
+{
+ CamelStream * volatile tcp_stream = NULL;
+ int ret;
+
+ CAMEL_TRY {
+#ifdef HAVE_SSL
+ const char *mode;
+#endif
+ unsigned char *buffer;
+ int len;
+ char *serv;
+ const char *port = NULL;
+ struct addrinfo *ai, hints = { 0 };
+ CamelException ex = { 0 };
+ CamelIMAPXCommand *ic;
+
+ if (is->url->port) {
+ serv = g_alloca(16);
+ sprintf(serv, "%d", is->url->port);
+ } else {
+ serv = "imap";
+ port = "143";
+ }
+#ifdef HAVE_SSL
+ mode = camel_url_get_param(is->url, "use_ssl");
+ if (mode && strcmp(mode, "never") != 0) {
+ if (!strcmp(mode, "when-possible")) {
+ tcp_stream = camel_tcp_stream_ssl_new_raw(is->session, is->url->host, STARTTLS_FLAGS);
+ } else {
+ if (is->url->port == 0) {
+ serv = "imaps";
+ port = "993";
+ }
+ tcp_stream = camel_tcp_stream_ssl_new(is->session, is->url->host, SSL_PORT_FLAGS);
+ }
+ } else {
+ tcp_stream = camel_tcp_stream_raw_new ();
+ }
+#else
+ tcp_stream = camel_tcp_stream_raw_new ();
+#endif /* HAVE_SSL */
+
+ hints.ai_socktype = SOCK_STREAM;
+ ai = camel_getaddrinfo(is->url->host, serv, &hints, &ex);
+ if (ex.id && ex.id != CAMEL_EXCEPTION_USER_CANCEL && port != NULL) {
+ camel_exception_clear(&ex);
+ ai = camel_getaddrinfo(is->url->host, port, &hints, &ex);
+ }
+
+ if (ex.id)
+ camel_exception_throw_ex(&ex);
+
+ ret = camel_tcp_stream_connect(CAMEL_TCP_STREAM(tcp_stream), ai);
+ camel_freeaddrinfo(ai);
+ if (ret == -1) {
+ if (errno == EINTR)
+ camel_exception_throw(CAMEL_EXCEPTION_USER_CANCEL, _("Connection cancelled"));
+ else
+ camel_exception_throw(CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
+ _("Could not connect to %s (port %s): %s"),
+ is->url->host, serv, g_strerror(errno));
+ }
+
+ is->stream = (CamelIMAPXStream *)camel_imapx_stream_new(tcp_stream);
+ camel_object_unref(tcp_stream);
+ tcp_stream = NULL;
+
+ camel_imapx_stream_gets(is->stream, &buffer, &len);
+ printf("Got greeting '%.*s'\n", len, buffer);
+
+ ic = camel_imapx_command_new("CAPABILITY", NULL, "CAPABILITY");
+ imapx_command_run(is, ic);
+ camel_imapx_command_free(ic);
+ } CAMEL_CATCH(e) {
+ if (tcp_stream)
+ camel_object_unref(tcp_stream);
+ camel_exception_throw_ex(e);
+ } CAMEL_DONE;
+}
+
+static void
+imapx_reconnect(CamelIMAPXServer *is)
+{
+retry:
+ CAMEL_TRY {
+ CamelSasl *sasl;
+ CamelIMAPXCommand *ic;
+
+ imapx_connect(is, 0, 0);
+
+ if (is->url->passwd == NULL) {
+ CamelException ex = { 0 };
+ char *prompt = g_strdup_printf(_("%sPlease enter the IMAP password for %s@%s"), "", is->url->user, is->url->host);
+
+ is->url->passwd = camel_session_get_password(is->session, (CamelService *)is->store,
+ camel_url_get_param(is->url, "auth-domain"),
+ prompt, "password", 0, &ex);
+ g_free(prompt);
+ if (ex.id)
+ camel_exception_throw_ex(&ex);
+ }
+
+ if (is->url->authmech
+ && (sasl = camel_sasl_new("imap", is->url->authmech, NULL))) {
+ ic = camel_imapx_command_new("AUTHENTICATE", NULL, "AUTHENTICATE %A", sasl);
+ camel_object_unref(sasl);
+ } else {
+ ic = camel_imapx_command_new("LOGIN", NULL, "LOGIN %s %s", is->url->user, is->url->passwd);
+ }
+
+ // TODO freeing data?
+ imapx_command_run(is, ic);
+ if (ic->status->result != IMAP_OK)
+ camel_exception_throw(CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE, "Login failed: %s", ic->status->text);
+ camel_imapx_command_free(ic);
+
+ /* After login we re-capa */
+ if (is->cinfo) {
+ imap_free_capability(is->cinfo);
+ is->cinfo = NULL;
+ }
+ ic = camel_imapx_command_new("CAPABILITY", NULL, "CAPABILITY");
+ imapx_command_run(is, ic);
+ camel_imapx_command_free(ic);
+ is->state = IMAPX_AUTHENTICATED;
+ } CAMEL_CATCH(e) {
+ /* Shrug, either way this re-loops back ... */
+ if (TRUE /*e->ex != CAMEL_EXCEPTION_USER_CANCEL*/) {
+ printf("Re Connection failed: %s\n", e->desc);
+ sleep(5);
+ // camelexception_done?
+ goto retry;
+ }
+ } CAMEL_DONE;
+}
+
+/* ********************************************************************** */
+
+static void
+imapx_job_done(CamelIMAPXServer *is, CamelIMAPXCommand *ic)
+{
+ CamelIMAPXJob *job = ic->job;
+
+ e_dlist_remove((EDListNode *)job);
+ if (job->noreply) {
+ camel_exception_clear(job->ex);
+ g_free(job);
+ } else
+ e_msgport_reply((EMsg *)job);
+}
+
+/* ********************************************************************** */
+
+static void
+imapx_job_get_message_done(CamelIMAPXServer *is, CamelIMAPXCommand *ic)
+{
+ CamelIMAPXJob *job = ic->job;
+
+ /* We either have more to fetch (partial mode?), we are complete,
+ or we failed. Failure is handled in the fetch code, so
+ we just return the job, or keep it alive with more requests */
+
+ // FIXME FIXME Who and how do we free the command???
+
+ job->commands--;
+
+ if (ic->status->result != IMAP_OK) {
+ job->u.get_message.body_len = -1;
+ if (job->u.get_message.stream) {
+ camel_object_unref(job->u.get_message.stream);
+ job->u.get_message.stream = 0;
+ }
+
+ camel_exception_setv(job->ex, 1, "Error retriving message: %s", ic->status->text);
+ }
+
+#ifdef MULTI_FETCH
+ if (job->u.get_message.body_len == MULTI_SIZE) {
+ ic = camel_imapx_command_new("FETCH", job->folder->full_name,
+ "UID FETCH %t (BODY.PEEK[]", job->u.get_message.uid);
+ camel_imapx_command_add(ic, "<%u.%u>", job->u.get_message.fetch_offset, MULTI_SIZE);
+ camel_imapx_command_add(ic, ")");
+ ic->complete = imapx_job_get_message_done;
+ ic->job = job;
+ ic->pri = job->pri;
+ job->u.get_message.fetch_offset += MULTI_SIZE;
+ job->commands++;
+ imapx_command_queue(is, ic);
+ }
+#endif
+ if (job->commands == 0)
+ imapx_job_done(is, ic);
+}
+
+static void
+imapx_job_get_message_start(CamelIMAPXServer *is, CamelIMAPXJob *job)
+{
+ CamelIMAPXCommand *ic;
+#ifdef MULTI_FETCH
+ int i;
+#endif
+ // FIXME: MUST ensure we never try to get the same message
+ // twice at the same time.
+
+ /* If this is a high-priority get, then we also
+ select the folder to make sure it runs immmediately ...
+
+ This doesn't work yet, so we always force a select every time
+ */
+ //imapx_select(is, job->folder);
+#ifdef MULTI_FETCH
+ for (i=0;i<3;i++) {
+ ic = camel_imapx_command_new("FETCH", job->folder->full_name,
+ "UID FETCH %t (BODY.PEEK[]", job->u.get_message.uid);
+ camel_imapx_command_add(ic, "<%u.%u>", job->u.get_message.fetch_offset, MULTI_SIZE);
+ camel_imapx_command_add(ic, ")");
+ ic->complete = imapx_job_get_message_done;
+ ic->job = job;
+ ic->pri = job->pri;
+ job->u.get_message.fetch_offset += MULTI_SIZE;
+ job->commands++;
+ imapx_command_queue(is, ic);
+ }
+#else
+ ic = camel_imapx_command_new("FETCH", job->folder->full_name,
+ "UID FETCH %t (BODY.PEEK[])", job->u.get_message.uid);
+ ic->complete = imapx_job_get_message_done;
+ ic->job = job;
+ ic->pri = job->pri;
+ job->commands++;
+ imapx_command_queue(is, ic);
+#endif
+}
+
+/* ********************************************************************** */
+
+static void
+imapx_job_append_message_done(CamelIMAPXServer *is, CamelIMAPXCommand *ic)
+{
+ CamelIMAPXJob *job = ic->job;
+ CamelMessageInfo *mi;
+ char *cur;
+
+ /* Append done. If we the server supports UIDPLUS we will get an APPENDUID response
+ with the new uid. This lets us move the message we have directly to the cache
+ and also create a correctly numbered MessageInfo, without losing any information.
+ Otherwise we have to wait for the server to less us know it was appended. */
+
+ if (ic->status->result == IMAP_OK) {
+ if (ic->status->condition == IMAP_APPENDUID) {
+ printf("Got appenduid %d %d\n", (int)ic->status->u.appenduid.uidvalidity, (int)ic->status->u.appenduid.uid);
+ if (ic->status->u.appenduid.uidvalidity == is->uidvalidity) {
+ mi = camel_message_info_clone(job->u.append_message.info);
+ mi->uid = g_strdup_printf("%u", (unsigned int)ic->status->u.appenduid.uid);
+ cur = imapx_get_path_uid(is, job->folder, NULL, mi->uid);
+ printf("Moving cache item %s to %s\n", job->u.append_message.path, cur);
+ link(job->u.append_message.path, cur);
+ g_free(cur);
+ camel_folder_summary_add(job->folder->summary, mi);
+ camel_message_info_free(mi);
+ } else {
+ printf("but uidvalidity changed, uh ...\n");
+ }
+ }
+ camel_folder_summary_remove(job->folder->summary, job->u.append_message.info);
+ // should the folder-summary remove the file ?
+ unlink(job->u.append_message.path);
+ } else {
+ camel_exception_setv(job->ex, 1, "Error appending message: %s", ic->status->text);
+ }
+
+ camel_message_info_free(job->u.append_message.info);
+ g_free(job->u.append_message.path);
+ camel_object_unref(job->folder);
+
+ imapx_job_done(is, ic);
+}
+
+static void
+imapx_job_append_message_start(CamelIMAPXServer *is, CamelIMAPXJob *job)
+{
+ CamelIMAPXCommand *ic;
+
+ // FIXME: We dont need anything selected for this command to run ...
+ //imapx_select(is, job->folder);
+ /* TODO: we could supply the original append date from the file timestamp */
+ ic = camel_imapx_command_new("APPEND", NULL,
+ "APPEND %f %F %P",
+ job->folder->full_name,
+ ((CamelMessageInfoBase *)job->u.append_message.info)->flags,
+ ((CamelMessageInfoBase *)job->u.append_message.info)->user_flags,
+ job->u.append_message.path);
+ ic->complete = imapx_job_append_message_done;
+ ic->job = job;
+ ic->pri = job->pri;
+ job->commands++;
+ imapx_command_queue(is, ic);
+}
+
+/* ********************************************************************** */
+
+static int
+imapx_refresh_info_uid_cmp(const void *ap, const void *bp)
+{
+ unsigned int av, bv;
+
+ av = strtoul((const char *)ap, NULL, 10);
+ bv = strtoul((const char *)bp, NULL, 10);
+
+ if (av<bv)
+ return -1;
+ else if (av>bv)
+ return 1;
+ else
+ return 0;
+}
+
+static int
+imapx_refresh_info_cmp(const void *ap, const void *bp)
+{
+ const struct _refresh_info *a = ap;
+ const struct _refresh_info *b = bp;
+
+ return imapx_refresh_info_uid_cmp(a->uid, b->uid);
+}
+
+/* skips over non-server uids (pending appends) */
+static const CamelMessageInfo *
+imapx_iterator_next(CamelIterator *iter, CamelException *ex)
+{
+ const CamelMessageInfo *iterinfo;
+
+ while ((iterinfo = camel_iterator_next(iter, NULL))
+ && strchr(camel_message_info_uid(iterinfo), '-') != NULL)
+ printf("Ignoring offline uid '%s'\n", camel_message_info_uid(iterinfo));
+
+ return iterinfo;
+}
+
+static void
+imapx_job_refresh_info_step_done(CamelIMAPXServer *is, CamelIMAPXCommand *ic)
+{
+ CamelIMAPXJob *job = ic->job;
+ int i = job->u.refresh_info.index;
+ GArray *infos = job->u.refresh_info.infos;
+
+ if (camel_change_info_changed(job->u.refresh_info.changes))
+ camel_object_trigger_event(job->folder, "folder_changed", job->u.refresh_info.changes);
+ camel_change_info_clear(job->u.refresh_info.changes);
+
+ if (i<infos->len) {
+ ic = camel_imapx_command_new("FETCH", job->folder->full_name, "UID FETCH ");
+ ic->complete = imapx_job_refresh_info_step_done;
+ ic->job = job;
+ job->u.refresh_info.last_index = i;
+
+ for (;i<infos->len;i++) {
+ int res;
+ struct _refresh_info *r = &g_array_index(infos, struct _refresh_info, i);
+
+ if (r->uid) {
+ res = imapx_uidset_add(&job->u.refresh_info.uidset, ic, r->uid);
+ if (res == 1) {
+ camel_imapx_command_add(ic, " (RFC822.SIZE RFC822.HEADER)");
+ job->u.refresh_info.index = i;
+ imapx_command_queue(is, ic);
+ return;
+ }
+ }
+ }
+
+ job->u.refresh_info.index = i;
+ if (imapx_uidset_done(&job->u.refresh_info.uidset, ic)) {
+ camel_imapx_command_add(ic, " (RFC822.SIZE RFC822.HEADER)");
+ imapx_command_queue(is, ic);
+ return;
+ }
+ }
+
+ for (i=0;i<infos->len;i++) {
+ struct _refresh_info *r = &g_array_index(infos, struct _refresh_info, i);
+
+ g_free(r->uid);
+ }
+ g_array_free(job->u.refresh_info.infos, TRUE);
+ e_dlist_remove((EDListNode *)job);
+ e_msgport_reply((EMsg *)job);
+}
+
+static void
+imapx_job_refresh_info_done(CamelIMAPXServer *is, CamelIMAPXCommand *ic)
+{
+ CamelIMAPXJob *job = ic->job;
+ int i;
+ GArray *infos = job->u.refresh_info.infos;
+
+ if (ic->status->result == IMAP_OK) {
+ GCompareDataFunc uid_cmp = CFS_CLASS(job->folder->summary)->uid_cmp;
+ CamelIterator *iter;
+ const CamelMessageInfo *iterinfo;
+ CamelIMAPXMessageInfo *info;
+ CamelFolderSummary *s = job->folder->summary;
+ int i, count=0;
+
+ /* Here we do the typical sort/iterate/merge loop.
+ If the server flags dont match what we had, we modify our
+ flags to pick up what the server now has - but we merge
+ not overwrite */
+
+ /* FIXME: We also have to check the offline directory for
+ anything missing in our summary, and also queue up jobs
+ for all outstanding messages to be uploaded */
+
+ qsort(infos->data, infos->len, sizeof(struct _refresh_info), imapx_refresh_info_cmp);
+
+ iter = camel_folder_summary_search(s, NULL, NULL, NULL, NULL);
+ iterinfo = imapx_iterator_next(iter, NULL);
+
+ for (i=0;i<infos->len;i++) {
+ struct _refresh_info *r = &g_array_index(infos, struct _refresh_info, i);
+
+ while (iterinfo && uid_cmp(camel_message_info_uid(iterinfo), r->uid, s) < 0) {
+ printf("Message %s vanished\n", iterinfo->uid);
+ camel_change_info_remove(job->u.refresh_info.changes, iterinfo);
+ camel_folder_summary_remove(s, (CamelMessageInfo *)iterinfo);
+ iterinfo = imapx_iterator_next(iter, NULL);
+ }
+
+ info = NULL;
+ if (iterinfo && uid_cmp(iterinfo->uid, r->uid, s) == 0) {
+ printf("already have '%s'\n", r->uid);
+ info = (CamelIMAPXMessageInfo *)iterinfo;
+ if (info->server_flags != r->server_flags
+ && camel_message_info_set_flags((CamelMessageInfo *)info, info->server_flags ^ r->server_flags, r->server_flags))
+ camel_change_info_change(job->u.refresh_info.changes, iterinfo);
+ iterinfo = imapx_iterator_next(iter, NULL);
+ g_free(r->uid);
+ r->uid = NULL;
+ } else {
+ count++;
+ }
+ }
+
+ while (iterinfo) {
+ printf("Message %s vanished\n", iterinfo->uid);
+ camel_change_info_remove(job->u.refresh_info.changes, iterinfo);
+ camel_folder_summary_remove(s, (CamelMessageInfo *)iterinfo);
+ iterinfo = imapx_iterator_next(iter, NULL);
+ }
+
+ if (camel_change_info_changed(job->u.refresh_info.changes))
+ camel_object_trigger_event(job->folder, "folder_changed", job->u.refresh_info.changes);
+ camel_change_info_clear(job->u.refresh_info.changes);
+
+ /* If we have any new messages, download their headers, but only a few (100?) at a time */
+ if (count) {
+ imapx_uidset_init(&job->u.refresh_info.uidset, 100, 0);
+ imapx_job_refresh_info_step_done(is, ic);
+ return;
+ }
+ } else {
+ camel_exception_setv(job->ex, 1, "Error retriving message: %s", ic->status->text);
+ }
+
+ for (i=0;i<infos->len;i++) {
+ struct _refresh_info *r = &g_array_index(infos, struct _refresh_info, i);
+
+ g_free(r->uid);
+ }
+ g_array_free(job->u.refresh_info.infos, TRUE);
+ e_dlist_remove((EDListNode *)job);
+ e_msgport_reply((EMsg *)job);
+}
+
+static void
+imapx_job_refresh_info_start(CamelIMAPXServer *is, CamelIMAPXJob *job)
+{
+ CamelIMAPXCommand *ic;
+
+ // temp ... we shouldn't/dont want to force select? or do we?
+ //imapx_select(is, job->folder);
+ ic = camel_imapx_command_new("FETCH", job->folder->full_name,
+ "FETCH 1:* (UID FLAGS)");
+ ic->job = job;
+ ic->complete = imapx_job_refresh_info_done;
+ job->u.refresh_info.infos = g_array_new(0, 0, sizeof(struct _refresh_info));
+ imapx_command_queue(is, ic);
+}
+
+/* ********************************************************************** */
+
+static void
+imapx_job_expunge_start(CamelIMAPXServer *is, CamelIMAPXJob *job)
+{
+ CamelIMAPXCommand *ic;
+
+ //imapx_select(is, job->folder);
+ ic = camel_imapx_command_new("EXPUNGE", job->folder->full_name, "EXPUNGE");
+ ic->job = job;
+ ic->complete = imapx_job_done;
+ imapx_command_queue(is, ic);
+}
+
+/* ********************************************************************** */
+
+static void
+imapx_job_list_start(CamelIMAPXServer *is, CamelIMAPXJob *job)
+{
+ CamelIMAPXCommand *ic;
+
+ ic = camel_imapx_command_new("LIST", NULL, "%s \"\" %s",
+ (job->u.list.flags & CAMEL_STORE_FOLDER_INFO_SUBSCRIBED)?"LSUB":"LIST",
+ job->u.list.pattern);
+ ic->pri = job->pri;
+ ic->job = job;
+ ic->complete = imapx_job_done;
+ imapx_command_queue(is, ic);
+}
+
+/* ********************************************************************** */
+
+/* FIXME: this is basically a copy of the same in camel-imapx-utils.c */
+static struct {
+ char *name;
+ guint32 flag;
+} flags_table[] = {
+ { "\\ANSWERED", CAMEL_MESSAGE_ANSWERED },
+ { "\\DELETED", CAMEL_MESSAGE_DELETED },
+ { "\\DRAFT", CAMEL_MESSAGE_DRAFT },
+ { "\\FLAGGED", CAMEL_MESSAGE_FLAGGED },
+ { "\\SEEN", CAMEL_MESSAGE_SEEN },
+ /* { "\\RECENT", CAMEL_IMAPX_MESSAGE_RECENT }, */
+};
+
+/*
+ flags 00101000
+ sflags 01001000
+ ^ 01100000
+~flags 11010111
+& 01000000
+
+&flags 00100000
+*/
+
+static void
+imapx_job_sync_changes_done(CamelIMAPXServer *is, CamelIMAPXCommand *ic)
+{
+ CamelIMAPXJob *job = ic->job;
+
+ job->commands--;
+
+ /* If this worked, we should really just update the changes that we sucessfully
+ stored, so we dont have to worry about sending them again ...
+ But then we'd have to track which uid's we actually updated, so its easier
+ just to refresh all of the ones we got.
+
+ Not that ... given all the asynchronicity going on, we're guaranteed
+ that what we just set is actually what is on the server now .. but
+ if it isn't, i guess we'll fix up next refresh */
+
+ if (ic->status->result != IMAP_OK && !camel_exception_is_set(job->ex))
+ camel_exception_setv(job->ex, 1, "Error syncing changes: %s", ic->status->text);
+
+ if (job->commands == 0) {
+ if (!camel_exception_is_set(job->ex)) {
+ int i;
+
+ for (i=0;i<job->u.sync_changes.infos->len;i++) {
+ CamelIMAPXMessageInfo *info = job->u.sync_changes.infos->pdata[i];
+
+ info->server_flags = ((CamelMessageInfoBase *)info)->flags & CAMEL_IMAPX_SERVER_FLAGS;
+
+ /* FIXME: move over user flags too */
+ }
+ }
+ e_dlist_remove((EDListNode *)job);
+ e_msgport_reply((EMsg *)job);
+ }
+}
+
+static void
+imapx_job_sync_changes_start(CamelIMAPXServer *is, CamelIMAPXJob *job)
+{
+ guint32 i, j;
+ struct _uidset_state ss;
+ GPtrArray *infos = job->u.sync_changes.infos;
+ int on;
+
+ for (on=0;on<2;on++) {
+ guint32 orset = on?job->u.sync_changes.on_set:job->u.sync_changes.off_set;
+ GArray *user_set = on?job->u.sync_changes.on_user:job->u.sync_changes.off_user;
+
+ for (j=0;j<sizeof(flags_table)/sizeof(flags_table[0]);j++) {
+ guint32 flag = flags_table[j].flag;
+ CamelIMAPXCommand *ic = NULL;
+
+ if ((orset & flag) == 0)
+ continue;
+
+ printf("checking/storing %s flags '%s'\n", on?"on":"off", flags_table[j].name);
+ imapx_uidset_init(&ss, 0, 100);
+ for (i=0;i<infos->len;i++) {
+ CamelIMAPXMessageInfo *info = infos->pdata[i];
+ guint32 flags = ((CamelMessageInfoBase *)info)->flags & CAMEL_IMAPX_SERVER_FLAGS;
+ guint32 sflags = info->server_flags & CAMEL_IMAPX_SERVER_FLAGS;
+ int send = 0;
+
+ if ( (on && (((flags ^ sflags) & flags) & flag))
+ || (!on && (((flags ^ sflags) & ~flags) & flag))) {
+ if (ic == NULL) {
+ ic = camel_imapx_command_new("STORE", job->folder->full_name, "UID STORE ");
+ ic->complete = imapx_job_sync_changes_done;
+ ic->job = job;
+ ic->pri = job->pri;
+ }
+ send = imapx_uidset_add(&ss, ic, camel_message_info_uid(info));
+ }
+ if (send || (i == infos->len-1 && imapx_uidset_done(&ss, ic))) {
+ job->commands++;
+ camel_imapx_command_add(ic, " %tFLAGS.SILENT (%t)", on?"+":"-", flags_table[j].name);
+ imapx_command_queue(is, ic);
+ ic = NULL;
+ }
+ }
+ }
+
+ if (user_set) {
+ CamelIMAPXCommand *ic = NULL;
+
+ for (j=0;j<user_set->len;j++) {
+ struct _imapx_flag_change *c = &g_array_index(user_set, struct _imapx_flag_change, i);
+
+ for (i=0;i<c->infos->len;i++) {
+ CamelIMAPXMessageInfo *info = c->infos->pdata[i];
+
+ if (ic == NULL) {
+ ic = camel_imapx_command_new("STORE", job->folder->full_name, "UID STORE ");
+ ic->complete = imapx_job_sync_changes_done;
+ ic->job = job;
+ ic->pri = job->pri;
+ }
+ if (imapx_uidset_add(&ss, ic, camel_message_info_uid(info))
+ || (i==c->infos->len-1 && imapx_uidset_done(&ss, ic))) {
+ job->commands++;
+ camel_imapx_command_add(ic, " %tFLAGS.SILENT (%t)", on?"+":"-", c->name);
+ imapx_command_queue(is, ic);
+ ic = NULL;
+ }
+ }
+ }
+ }
+ }
+
+ /* Since this may start in another thread ... we need to
+ lock the commands count, ho hum */
+
+ if (job->commands == 0) {
+ printf("Hmm, we didn't have any work to do afterall? hmm, this isn't right\n");
+
+ e_dlist_remove((EDListNode *)job);
+ e_msgport_reply((EMsg *)job);
+ }
+}
+
+/* ********************************************************************** */
+
+static void *
+imapx_server_loop(void *d)
+{
+ CamelIMAPXServer *is = d;
+ CamelIMAPXJob *job;
+
+ /*
+ The main processing (reading) loop.
+
+ Incoming requests are added as jobs and tasks from other threads,
+ we just read the results from the server continously, and match
+ them up with the queued tasks as they come back.
+
+ Of course this loop can also initiate its own commands as well.
+
+ So, multiple threads can submit jobs, and write to the
+ stream (issue: locking stream for write?), but only this
+ thread can ever read from the stream. This simplifies
+ locking, and greatly simplifies working out when new
+ work is ready.
+ */
+
+ printf("imapx server loop started\n");
+
+ // FIXME: handle exceptions
+ while (1) {
+ CAMEL_TRY {
+ if (!is->stream)
+ imapx_reconnect(is);
+
+ job = (CamelIMAPXJob *)e_msgport_get(is->port);
+ if (job) {
+ e_dlist_addtail(&is->jobs, (EDListNode *)job);
+ job->start(is, job);
+ }
+
+ if (!e_dlist_empty(&is->active)
+ || camel_imapx_stream_buffered(is->stream))
+ imapx_step(is);
+ else
+ e_msgport_wait(is->port);
+#if 0
+ /* TODO:
+ This poll stuff wont work - we might block
+ waiting for results inside loops etc.
+
+ Requires a different approach:
+
+ New commands are queued in other threads as well
+ as this thread, and get pipelined over the socket.
+
+ Main area of locking required is command_queue
+ and command_start_next, the 'literal' command,
+ the jobs queue, the active queue, the queue
+ queue. */
+
+ /* if ssl stream ... */
+#ifdef HAVE_SSL
+ {
+ PRPollDesc pollfds[2] = { };
+ int res;
+
+ printf("\nGoing to sleep waiting for work to do\n\n");
+
+ pollfds[0].fd = camel_tcp_stream_ssl_sockfd((CamelTcpStreamSSL *)is->stream->source);
+ pollfds[0].in_flags = PR_POLL_READ;
+ pollfds[1].fd = e_msgport_prfd(is->port);
+ pollfds[1].in_flags = PR_POLL_READ;
+
+ res = PR_Poll(pollfds, 2, PR_TicksPerSecond() / 10);
+ if (res == -1)
+ sleep(1) /* ?? */ ;
+ else if ((pollfds[0].out_flags & PR_POLL_READ)) {
+ printf(" * woken * have data ready\n");
+ do {
+ /* This is quite shitty, it will often block on each
+ part of the decode, causing significant
+ processing delays. */
+ imapx_step(is);
+ } while (camel_imapx_stream_buffered(is->stream));
+ } else if (pollfds[1].out_flags & PR_POLL_READ) {
+ printf(" * woken * have new job\n");
+ /* job is handled in main loop */
+ }
+ }
+#else
+ {
+ struct pollfd[2] = { 0 };
+ int res;
+
+ pollfd[0].fd = ((CamelTcpStreamRaw *)is->stream->source)->sockfd;
+ pollfd[0].events = POLLIN;
+ pollfd[1].fd = e_msgport_fd(is->port);
+ pollfd[1].events = POLLIN;
+
+ res = poll(pollfd, 2, 1000*30);
+ if (res == -1)
+ sleep(1) /* ?? */ ;
+ else if (res == 0)
+ /* timed out */;
+ else if (pollfds[0].revents & POLLIN) {
+ do {
+ imapx_step(is);
+ } while (camel_imapx_stream_buffered(is->stream));
+ }
+ }
+#endif
+#endif
+ } CAMEL_CATCH(e) {
+ printf("######### Got main loop exception: %s\n", e->desc);
+ sleep(1);
+ } CAMEL_DONE;
+ }
+
+ return NULL;
+}
+
+static void
+imapx_server_class_init(CamelIMAPXServerClass *ieclass)
+{
+ ieclass->tagprefix = 'A';
+
+// camel_object_class_add_event((CamelObjectClass *)ieclass, "status", NULL);
+}
+
+static void
+imapx_server_init(CamelIMAPXServer *ie, CamelIMAPXServerClass *ieclass)
+{
+ e_dlist_init(&ie->queue);
+ e_dlist_init(&ie->active);
+ e_dlist_init(&ie->done);
+ e_dlist_init(&ie->jobs);
+
+ ie->queue_lock = g_mutex_new();
+
+ ie->tagprefix = ieclass->tagprefix;
+ ieclass->tagprefix++;
+ if (ieclass->tagprefix > 'Z')
+ ieclass->tagprefix = 'A';
+ ie->tagprefix = 'A';
+
+ ie->state = IMAPX_DISCONNECTED;
+
+ ie->port = e_msgport_new();
+
+ ie->expunged = g_array_new(FALSE, FALSE, sizeof(guint32));
+}
+
+static void
+imapx_server_finalise(CamelIMAPXServer *ie, CamelIMAPXServerClass *ieclass)
+{
+ g_mutex_free(ie->queue_lock);
+
+ g_array_free(ie->expunged, TRUE);
+}
+
+CamelType
+camel_imapx_server_get_type (void)
+{
+ static CamelType type = CAMEL_INVALID_TYPE;
+
+ if (type == CAMEL_INVALID_TYPE) {
+ type = camel_type_register (
+ camel_object_get_type (),
+ "CamelIMAPXServer",
+ sizeof (CamelIMAPXServer),
+ sizeof (CamelIMAPXServerClass),
+ (CamelObjectClassInitFunc) imapx_server_class_init,
+ NULL,
+ (CamelObjectInitFunc) imapx_server_init,
+ (CamelObjectFinalizeFunc) imapx_server_finalise);
+ }
+
+ return type;
+}
+
+CamelIMAPXServer *
+camel_imapx_server_new(CamelStore *store, CamelURL *url)
+{
+ CamelIMAPXServer *is = (CamelIMAPXServer *)camel_object_new(camel_imapx_server_get_type());
+
+ is->session = ((CamelService *)store)->session;
+ camel_object_ref(is->session);
+ is->store = store;
+
+ is->url = camel_url_copy(url);
+ camel_url_set_user(is->url, "camel");
+ camel_url_set_passwd(is->url, "camel");
+
+ return is;
+}
+
+/* Client commands */
+
+void
+camel_imapx_server_connect(CamelIMAPXServer *is, int state)
+{
+ if (state) {
+ pthread_t id;
+
+ pthread_create(&id, NULL, imapx_server_loop, is);
+ } else {
+ /* tell processing thread to die, and wait till it does? */
+ }
+}
+
+static void
+imapx_run_job(CamelIMAPXServer *is, CamelIMAPXJob *job)
+{
+ EMsgPort *reply;
+
+ if (!job->noreply) {
+ reply = e_msgport_new();
+ job->msg.reply_port = reply;
+ }
+
+ /* Umm, so all these jobs 'select' first, which means reading(!)
+ we can't read from this thread ... hrm ... */
+ if (FALSE /*is->state >= IMAPX_AUTHENTICATED*/) {
+ /* NB: Must catch exceptions, cleanup/etc if we fail here? */
+ QUEUE_LOCK(is);
+ e_dlist_addhead(&is->jobs, (EDListNode *)job);
+ QUEUE_UNLOCK(is);
+ job->start(is, job);
+ } else {
+ e_msgport_put(is->port, (EMsg *)job);
+ }
+
+ if (!job->noreply) {
+ e_msgport_wait(reply);
+ g_assert(e_msgport_get(reply) == (EMsg *)job);
+ e_msgport_destroy(reply);
+ }
+}
+
+static CamelStream *
+imapx_server_get_message(CamelIMAPXServer *is, CamelFolder *folder, const char *uid, int pri, CamelException *ex)
+{
+ CamelStream *stream;
+ CamelIMAPXJob *job;
+ char *tmp, *name;
+
+ /* Get a message, we either get it from the local cache,
+ Or we ask for it, which will put it in the local cache,
+ then return that copy */
+
+ /* FIXME: The storage logic should use camel-data-cache,
+ which handles concurrent adds properly.
+ EXCEPT! It wont handle the 'new' dir directly ... do we care? */
+
+ name = imapx_get_path_uid(is, folder, NULL, uid);
+ stream = camel_stream_fs_new_with_name(name, O_RDONLY, 0);
+ if (stream) {
+ g_free(name);
+ return stream;
+ } else if (strchr(uid, '-')) {
+ camel_exception_setv(ex, 2, "Offline message vanished from disk: %s", uid);
+ g_free(name);
+ camel_object_unref(stream);
+ return NULL;
+ }
+
+ tmp = imapx_get_path_uid(is, folder, "tmp", uid);
+
+ job = g_malloc0(sizeof(*job));
+ job->pri = pri;
+ job->type = IMAPX_JOB_GET_MESSAGE;
+ job->start = imapx_job_get_message_start;
+ job->folder = folder;
+ job->u.get_message.uid = (char *)uid;
+ job->u.get_message.stream = camel_stream_fs_new_with_name(tmp, O_WRONLY|O_CREAT|O_TRUNC, 0666);
+ if (job->u.get_message.stream == NULL) {
+ g_free(tmp);
+ tmp = NULL;
+ job->u.get_message.stream = camel_stream_mem_new();
+ }
+ job->ex = ex;
+
+ imapx_run_job(is, job);
+
+ stream = job->u.get_message.stream;
+ g_free(job);
+
+ if (stream) {
+ if (tmp == NULL)
+ camel_stream_reset(stream);
+ else {
+ if (camel_stream_flush(stream) == 0 && camel_stream_close(stream) == 0) {
+ camel_object_unref(stream);
+ stream = NULL;
+ if (link(tmp, name) == 0)
+ stream = camel_stream_fs_new_with_name(name, O_RDONLY, 0);
+ } else {
+ camel_exception_setv(ex, 1, "closing tmp stream failed: %s", g_strerror(errno));
+ camel_object_unref(stream);
+ stream = NULL;
+ }
+ unlink(tmp);
+ }
+ }
+
+ g_free(tmp);
+ g_free(name);
+
+ return stream;
+}
+
+CamelStream *
+camel_imapx_server_get_message(CamelIMAPXServer *is, CamelFolder *folder, const char *uid, CamelException *ex)
+{
+ return imapx_server_get_message(is, folder, uid, 100, ex);
+}
+
+void
+camel_imapx_server_append_message(CamelIMAPXServer *is, CamelFolder *folder, CamelMimeMessage *message, const CamelMessageInfo *mi, CamelException *ex)
+{
+ char *uid = NULL, *tmp = NULL, *new = NULL;
+ CamelStream *stream, *filter;
+ CamelMimeFilter *canon;
+ CamelIMAPXJob *job;
+ CamelMessageInfo *info;
+ int res;
+
+ /* Append just assumes we have no/a dodgy connection. We dump stuff into the 'new'
+ directory, and let the summary know it's there. Then we fire off a no-reply
+ job which will asynchronously upload the message at some point in the future,
+ and fix up the summary to match */
+
+ // FIXME: assign a real uid! start with last known uid, add some maildir-like stuff?
+ do {
+ static int nextappend;
+
+ g_free(uid);
+ g_free(tmp);
+ uid = g_strdup_printf("%s-%d", "1000", nextappend++);
+ tmp = imapx_get_path_uid(is, folder, "tmp", uid);
+ stream = camel_stream_fs_new_with_name(tmp, O_WRONLY|O_CREAT|O_EXCL, 0666);
+ } while (stream == NULL && (errno == EINTR || errno == EEXIST));
+
+ if (stream == NULL) {
+ camel_exception_setv(ex, 2, "Cannot create spool file: %s", g_strerror(errno));
+ goto fail;
+ }
+
+ filter = (CamelStream *)camel_stream_filter_new_with_stream(stream);
+ camel_object_unref(stream);
+ canon = camel_mime_filter_canon_new(CAMEL_MIME_FILTER_CANON_CRLF);
+ camel_stream_filter_add((CamelStreamFilter *)filter, canon);
+ res = camel_data_wrapper_write_to_stream((CamelDataWrapper *)message, filter);
+ camel_object_unref(canon);
+ camel_object_unref(filter);
+
+ if (res == -1) {
+ camel_exception_setv(ex, 2, "Cannot create spool file: %s", g_strerror(errno));
+ goto fail;
+ }
+
+ new = imapx_get_path_uid(is, folder, "new", uid);
+ if (link(tmp, new) == -1) {
+ camel_exception_setv(ex, 2, "Cannot create spool file: %s", g_strerror(errno));
+ goto fail;
+ }
+
+ info = camel_message_info_new_from_message(folder->summary, message, mi);
+ info->uid = uid;
+ uid = NULL;
+ camel_folder_summary_add(folder->summary, info);
+
+ // FIXME
+
+ /* So, we actually just want to let the server loop that
+ messages need appending, i think. This is so the same
+ mechanism is used for normal uploading as well as
+ offline re-syncing when we go back online */
+
+ job = g_malloc0(sizeof(*job));
+ job->pri = -60;
+ job->type = IMAPX_JOB_APPEND_MESSAGE;
+ job->noreply = 1;
+ job->start = imapx_job_append_message_start;
+ job->folder = folder;
+ camel_object_ref(folder);
+ job->u.append_message.info = info;
+ job->u.append_message.path = new;
+ new = NULL;
+
+ imapx_run_job(is, job);
+fail:
+ if (tmp)
+ unlink(tmp);
+ g_free(uid);
+ g_free(tmp);
+ g_free(new);
+}
+
+#include "camel-imapx-store.h"
+
+static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
+static CamelIterator *threaditer;
+static CamelFolder *threadfolder;
+
+static void *
+getmsgs(void *d)
+{
+ CamelIMAPXServer *is = ((CamelIMAPXStore *)threadfolder->parent_store)->server;
+ const CamelMessageInfo *info;
+ CamelException ex = { 0 };
+
+ /* FIXME: detach? */
+
+ printf("Checking thread, downloading messages in the background ...\n");
+
+ pthread_mutex_lock(&lock);
+ while ((info = imapx_iterator_next(threaditer, NULL))) {
+ char *cur = imapx_get_path_uid(is, threadfolder, NULL, camel_message_info_uid(info));
+ char *uid = g_strdup(camel_message_info_uid(info));
+ struct stat st;
+
+ pthread_mutex_unlock(&lock);
+
+ if (stat(cur, &st) == -1 && errno == ENOENT) {
+ CamelStream *stream;
+
+ printf(" getting uncached message '%s'\n", uid);
+ stream = imapx_server_get_message(is, threadfolder, uid, -100, &ex);
+ if (stream)
+ camel_object_unref(stream);
+ camel_exception_clear(&ex);
+ } else
+ printf(" already cached message '%s'\n", uid);
+
+ g_free(uid);
+ g_free(cur);
+ pthread_mutex_lock(&lock);
+ }
+ pthread_mutex_unlock(&lock);
+
+ return NULL;
+}
+
+void
+camel_imapx_server_refresh_info(CamelIMAPXServer *is, CamelFolder *folder, CamelException *ex)
+{
+ CamelIMAPXJob *job;
+
+ job = g_malloc0(sizeof(*job));
+ job->type = IMAPX_JOB_REFRESH_INFO;
+ job->start = imapx_job_refresh_info_start;
+ job->folder = folder;
+ job->ex = ex;
+ job->u.refresh_info.changes = camel_change_info_new(NULL);
+
+ imapx_run_job(is, job);
+
+ if (camel_change_info_changed(job->u.refresh_info.changes))
+ camel_object_trigger_event(folder, "folder_changed", job->u.refresh_info.changes);
+ camel_change_info_free(job->u.refresh_info.changes);
+
+ g_free(job);
+
+ {
+ int i;
+ int c = 3;
+ pthread_t ids[10];
+
+ threadfolder = folder;
+ threaditer = camel_folder_search(folder, NULL, NULL, NULL, NULL);
+ for (i=0;i<c;i++)
+ pthread_create(&ids[i], NULL, getmsgs, NULL);
+
+ for (i=0;i<c;i++)
+ pthread_join(ids[i], NULL);
+ camel_iterator_free(threaditer);
+ }
+}
+
+static void
+imapx_sync_free_user(GArray *user_set)
+{
+ int i;
+
+ if (user_set == NULL)
+ return;
+
+ for (i=0;i<user_set->len;i++)
+ g_ptr_array_free(g_array_index(user_set, struct _imapx_flag_change, i).infos, TRUE);
+ g_array_free(user_set, TRUE);
+}
+
+void
+camel_imapx_server_sync_changes(CamelIMAPXServer *is, CamelFolder *folder, GPtrArray *infos, CamelException *ex)
+{
+ guint i, on_orset, off_orset;
+ GArray *on_user = NULL, *off_user = NULL;
+ CamelIMAPXMessageInfo *info;
+ CamelIMAPXJob *job;
+
+ /* We calculate two masks, a mask of all flags which have been
+ turned off and a mask of all flags which have been turned
+ on. If either of these aren't 0, then we have work to do,
+ and we fire off a job to do it.
+
+ User flags are a bit more tricky, we rely on the user
+ flags being sorted, and then we create a bunch of lists;
+ one for each flag being turned off, including each
+ info being turned off, and one for each flag being turned on.
+ */
+
+ off_orset = on_orset = 0;
+ for (i=0;i<infos->len;i++) {
+ guint32 flags, sflags;
+ CamelFlag *uflags, *suflags;
+
+ info = infos->pdata[i];
+ flags = ((CamelMessageInfoBase *)info)->flags & CAMEL_IMAPX_SERVER_FLAGS;
+ sflags = info->server_flags & CAMEL_IMAPX_SERVER_FLAGS;
+ if (flags != sflags) {
+ off_orset |= ( flags ^ sflags ) & ~flags;
+ on_orset |= (flags ^ sflags) & flags;
+ }
+
+ uflags = ((CamelMessageInfoBase *)info)->user_flags;
+ suflags = info->server_user_flags;
+ while (uflags || suflags) {
+ int res;
+
+ if (uflags) {
+ if (suflags)
+ res = strcmp(uflags->name, suflags->name);
+ else
+ res = -1;
+ } else {
+ res = 1;
+ }
+
+ if (res == 0) {
+ uflags = uflags->next;
+ suflags = suflags->next;
+ } else {
+ GArray *user_set;
+ CamelFlag *user_flag;
+ struct _imapx_flag_change *change, add;
+
+ if (res < 0) {
+ if (on_user == NULL)
+ on_user = g_array_new(sizeof(*change), 0, 0);
+ user_set = on_user;
+ user_flag = uflags;
+ uflags = uflags->next;
+ } else {
+ if (off_user == NULL)
+ off_user = g_array_new(sizeof(*change), 0, 0);
+ user_set = off_user;
+ user_flag = suflags;
+ suflags = suflags->next;
+ }
+
+ /* Could sort this and binary search */
+ for (i=0;i<user_set->len;i++) {
+ change = &g_array_index(user_set, struct _imapx_flag_change, i);
+ if (strcmp(change->name, user_flag->name) == 0)
+ goto found;
+ }
+ add.name = g_strdup(user_flag->name);
+ add.infos = g_ptr_array_new();
+ g_array_append_val(user_set, add);
+ change = &add;
+ found:
+ g_ptr_array_add(change->infos, info);
+ }
+ }
+ }
+
+ if ((on_orset|off_orset) == 0 && on_user == NULL && off_user == NULL)
+ return;
+
+ job = g_malloc0(sizeof(*job));
+ job->type = IMAPX_JOB_SYNC_CHANGES;
+ job->start = imapx_job_sync_changes_start;
+ job->pri = -50;
+ job->folder = folder;
+ job->ex = ex;
+ job->u.sync_changes.infos = infos;
+ job->u.sync_changes.on_set = on_orset;
+ job->u.sync_changes.off_set = off_orset;
+ job->u.sync_changes.on_user = on_user;
+ job->u.sync_changes.off_user = off_user;
+
+ imapx_run_job(is, job);
+
+ g_free(job);
+
+ imapx_sync_free_user(on_user);
+ imapx_sync_free_user(off_user);
+}
+
+/* expunge-uids? */
+void
+camel_imapx_server_expunge(CamelIMAPXServer *is, CamelFolder *folder, CamelException *ex)
+{
+ CamelIMAPXJob *job;
+
+ /* Do we really care to wait for this one to finish? */
+
+ job = g_malloc0(sizeof(*job));
+ job->type = IMAPX_JOB_EXPUNGE;
+ job->start = imapx_job_expunge_start;
+ job->pri = -120;
+ job->folder = folder;
+ job->ex = ex;
+
+ imapx_run_job(is, job);
+
+ g_free(job);
+}
+
+static guint
+imapx_name_hash(gconstpointer key)
+{
+ if (g_ascii_strcasecmp(key, "INBOX") == 0)
+ return g_str_hash("INBOX");
+ else
+ return g_str_hash(key);
+}
+
+static gint
+imapx_name_equal(gconstpointer a, gconstpointer b)
+{
+ gconstpointer aname = a, bname = b;
+
+ if (g_ascii_strcasecmp(a, "INBOX") == 0)
+ aname = "INBOX";
+ if (g_ascii_strcasecmp(b, "INBOX") == 0)
+ bname = "INBOX";
+ return g_str_equal(aname, bname);
+}
+
+static void
+imapx_list_flatten(void *k, void *v, void *d)
+{
+ GPtrArray *folders = d;
+
+ g_ptr_array_add(folders, v);
+}
+
+static int
+imapx_list_cmp(const void *ap, const void *bp)
+{
+ struct _list_info *a = ((struct _list_info **)ap)[0];
+ struct _list_info *b = ((struct _list_info **)bp)[0];
+
+ return strcmp(a->name, b->name);
+}
+
+GPtrArray *
+camel_imapx_server_list(CamelIMAPXServer *is, const char *top, guint32 flags, CamelException *ex)
+{
+ CamelIMAPXJob *job;
+ GPtrArray *folders;
+
+ job = g_malloc0(sizeof(*job));
+ job->type = IMAPX_JOB_LIST;
+ job->start = imapx_job_list_start;
+ job->pri = -80;
+ job->ex = ex;
+ job->u.list.flags = flags;
+ job->u.list.folders = g_hash_table_new(imapx_name_hash, imapx_name_equal);
+ job->u.list.pattern = g_alloca(strlen(top)+5);
+ if (flags & CAMEL_STORE_FOLDER_INFO_RECURSIVE)
+ sprintf(job->u.list.pattern, "%s*", top);
+ else
+ sprintf(job->u.list.pattern, "%s", top);
+
+ imapx_run_job(is, job);
+
+ folders = g_ptr_array_new();
+ g_hash_table_foreach(job->u.list.folders, imapx_list_flatten, folders);
+ g_hash_table_destroy(job->u.list.folders);
+
+ g_free(job);
+
+ qsort(folders->pdata, folders->len, sizeof(folders->pdata[0]), imapx_list_cmp);
+
+ return folders;
+}
diff --git a/camel/providers/imap/camel-imapx-server.h b/camel/providers/imap/camel-imapx-server.h
new file mode 100644
index 000000000..633606cd5
--- /dev/null
+++ b/camel/providers/imap/camel-imapx-server.h
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2005 Novell Inc.
+ *
+ * Authors:
+ * Michael Zucchi <notzed@ximian.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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 Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef _CAMEL_IMAPX_SERVER_H
+#define _CAMEL_IMAPX_SERVER_H
+
+#include <libedataserver/e-msgport.h>
+
+struct _CamelFolder;
+struct _CamelException;
+struct _CamelMimeMessage;
+struct _CamelMessageInfo;
+
+#define CAMEL_IMAPX_SERVER(obj) CAMEL_CHECK_CAST (obj, camel_imapx_server_get_type (), CamelIMAPPServer)
+#define CAMEL_IMAPX_SERVER_CLASS(klass) CAMEL_CHECK_CLASS_CAST (klass, camel_imapx_server_get_type (), CamelIMAPPServerClass)
+#define CAMEL_IS_IMAPX_SERVER(obj) CAMEL_CHECK_TYPE (obj, camel_imapx_server_get_type ())
+
+typedef struct _CamelIMAPXServer CamelIMAPXServer;
+typedef struct _CamelIMAPXServerClass CamelIMAPXServerClass;
+
+#define IMAPX_MODE_READ (1<<0)
+#define IMAPX_MODE_WRITE (1<<1)
+
+struct _CamelIMAPXServer {
+ CamelObject cobject;
+
+ struct _CamelStore *store;
+ struct _CamelSession *session;
+
+ /* Info about the current connection */
+ struct _CamelURL *url;
+ struct _CamelIMAPXStream *stream;
+ struct _capability_info *cinfo;
+
+ /* incoming jobs */
+ EMsgPort *port;
+ EDList jobs;
+
+ char tagprefix;
+ int state:4;
+
+ /* Current command/work queue. All commands are stored in one list,
+ all the time, so they can be cleaned up in exception cases */
+ void *queue_lock;
+ struct _CamelIMAPXCommand *literal;
+ EDList queue;
+ EDList active;
+ EDList done;
+
+ /* info on currently selected folder */
+ struct _CamelFolder *select_folder;
+ char *select;
+ struct _CamelFolderChangeInfo *changes;
+ struct _CamelFolder *select_pending;
+ guint32 permanentflags;
+ guint32 uidvalidity;
+ guint32 unseen;
+ guint32 exists;
+ guint32 recent;
+ guint32 mode;
+
+ /* any expunges that happened from the last command, they are
+ processed after the command completes. */
+ GArray *expunged;
+};
+
+struct _CamelIMAPXServerClass {
+ CamelObjectClass cclass;
+
+ char tagprefix;
+};
+
+CamelType camel_imapx_server_get_type (void);
+CamelIMAPXServer *camel_imapx_server_new(struct _CamelStore *store, struct _CamelURL *url);
+
+void camel_imapx_server_connect(CamelIMAPXServer *is, int state);
+
+GPtrArray *camel_imapx_server_list(CamelIMAPXServer *is, const char *top, guint32 flags, CamelException *ex);
+
+void camel_imapx_server_refresh_info(CamelIMAPXServer *is, CamelFolder *folder, struct _CamelException *ex);
+void camel_imapx_server_sync_changes(CamelIMAPXServer *is, CamelFolder *folder, GPtrArray *infos, CamelException *ex);
+void camel_imapx_server_expunge(CamelIMAPXServer *is, CamelFolder *folder, CamelException *ex);
+
+CamelStream *camel_imapx_server_get_message(CamelIMAPXServer *is, CamelFolder *folder, const char *uid, struct _CamelException *ex);
+void camel_imapx_server_append_message(CamelIMAPXServer *is, CamelFolder *folder, struct _CamelMimeMessage *message, const struct _CamelMessageInfo *mi, CamelException *ex);
+
+#endif /* ! _CAMEL_IMAPX_SERVER_H */
diff --git a/camel/providers/imap/camel-imapx-stream.c b/camel/providers/imap/camel-imapx-stream.c
new file mode 100644
index 000000000..b9a7c27ac
--- /dev/null
+++ b/camel/providers/imap/camel-imapx-stream.c
@@ -0,0 +1,728 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; fill-column: 160 -*-
+ *
+ * Author:
+ * Michael Zucchi <notzed@ximian.com>
+ *
+ * Copyright 1999, 2000 Ximian, Inc. (www.ximian.com)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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 Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <errno.h>
+
+#include <glib.h>
+
+#include <camel/camel-stream-mem.h>
+
+#include "camel-imapx-utils.h"
+#include "camel-imapx-stream.h"
+#include "camel-imapx-exception.h"
+
+#define t(x)
+#define io(x) x
+
+static CamelObjectClass *parent_class = NULL;
+
+/* Returns the class for a CamelStream */
+#define CS_CLASS(so) CAMEL_IMAPX_STREAM_CLASS(CAMEL_OBJECT_GET_CLASS(so))
+
+#define CAMEL_IMAPX_STREAM_SIZE (4096)
+#define CAMEL_IMAPX_STREAM_TOKEN (4096) /* maximum token size */
+
+static int
+stream_fill(CamelIMAPXStream *is)
+{
+ int left = 0;
+
+ if (is->source) {
+ left = is->end - is->ptr;
+ memcpy(is->buf, is->ptr, left);
+ is->end = is->buf + left;
+ is->ptr = is->buf;
+ left = camel_stream_read(is->source, is->end, CAMEL_IMAPX_STREAM_SIZE - (is->end - is->buf));
+ if (left > 0) {
+ is->end += left;
+ io(printf("camel_imapx_read: buffer is '%.*s'\n", (int)(is->end - is->ptr), is->ptr));
+ return is->end - is->ptr;
+ } else {
+ io(printf("camel_imapx_read: -1\n"));
+ return -1;
+ }
+ }
+
+ printf("camel_imapx_read: -1\n");
+
+ return -1;
+}
+
+static ssize_t
+stream_read(CamelStream *stream, char *buffer, size_t n)
+{
+ CamelIMAPXStream *is = (CamelIMAPXStream *)stream;
+ ssize_t max;
+
+ if (is->literal == 0 || n == 0)
+ return 0;
+
+ max = is->end - is->ptr;
+ if (max > 0) {
+ max = MIN(max, is->literal);
+ max = MIN(max, n);
+ memcpy(buffer, is->ptr, max);
+ is->ptr += max;
+ } else {
+ max = MIN(is->literal, n);
+ max = camel_stream_read(is->source, buffer, max);
+ if (max <= 0)
+ return max;
+ }
+
+ io(printf("camel_imapx_read(literal): '%.*s'\n", (int)max, buffer));
+
+ is->literal -= max;
+
+ return max;
+}
+
+static ssize_t
+stream_write(CamelStream *stream, const char *buffer, size_t n)
+{
+ CamelIMAPXStream *is = (CamelIMAPXStream *)stream;
+
+ io(printf("camel_imapx_write: '%.*s'\n", (int)n, buffer));
+
+ return camel_stream_write(is->source, buffer, n);
+}
+
+static int
+stream_close(CamelStream *stream)
+{
+ /* nop? */
+ return 0;
+}
+
+static int
+stream_flush(CamelStream *stream)
+{
+ /* nop? */
+ return 0;
+}
+
+static gboolean
+stream_eos(CamelStream *stream)
+{
+ CamelIMAPXStream *is = (CamelIMAPXStream *)stream;
+
+ return is->literal == 0;
+}
+
+static int
+stream_reset(CamelStream *stream)
+{
+ /* nop? reset literal mode? */
+ return 0;
+}
+
+static void
+camel_imapx_stream_class_init (CamelStreamClass *camel_imapx_stream_class)
+{
+ CamelStreamClass *camel_stream_class = (CamelStreamClass *)camel_imapx_stream_class;
+
+ parent_class = camel_type_get_global_classfuncs( CAMEL_OBJECT_TYPE );
+
+ /* virtual method definition */
+ camel_stream_class->read = stream_read;
+ camel_stream_class->write = stream_write;
+ camel_stream_class->close = stream_close;
+ camel_stream_class->flush = stream_flush;
+ camel_stream_class->eos = stream_eos;
+ camel_stream_class->reset = stream_reset;
+}
+
+static void
+camel_imapx_stream_init(CamelIMAPXStream *is, CamelIMAPXStreamClass *isclass)
+{
+ /* +1 is room for appending a 0 if we need to for a token */
+ is->ptr = is->end = is->buf = g_malloc(CAMEL_IMAPX_STREAM_SIZE+1);
+ is->tokenptr = is->tokenbuf = g_malloc(CAMEL_IMAPX_STREAM_SIZE+1);
+ is->tokenend = is->tokenbuf + CAMEL_IMAPX_STREAM_SIZE;
+}
+
+static void
+camel_imapx_stream_finalise(CamelIMAPXStream *is)
+{
+ g_free(is->buf);
+ if (is->source)
+ camel_object_unref((CamelObject *)is->source);
+}
+
+CamelType
+camel_imapx_stream_get_type (void)
+{
+ static CamelType camel_imapx_stream_type = CAMEL_INVALID_TYPE;
+
+ if (camel_imapx_stream_type == CAMEL_INVALID_TYPE) {
+ camel_imapx_stream_type = camel_type_register( camel_stream_get_type(),
+ "CamelIMAPXStream",
+ sizeof( CamelIMAPXStream ),
+ sizeof( CamelIMAPXStreamClass ),
+ (CamelObjectClassInitFunc) camel_imapx_stream_class_init,
+ NULL,
+ (CamelObjectInitFunc) camel_imapx_stream_init,
+ (CamelObjectFinalizeFunc) camel_imapx_stream_finalise );
+ }
+
+ return camel_imapx_stream_type;
+}
+
+/**
+ * camel_imapx_stream_new:
+ *
+ * Returns a NULL stream. A null stream is always at eof, and
+ * always returns success for all reads and writes.
+ *
+ * Return value: the stream
+ **/
+CamelStream *
+camel_imapx_stream_new(CamelStream *source)
+{
+ CamelIMAPXStream *is;
+
+ is = (CamelIMAPXStream *)camel_object_new(camel_imapx_stream_get_type ());
+ camel_object_ref((CamelObject *)source);
+ is->source = source;
+
+ return (CamelStream *)is;
+}
+
+/* Returns if there is any data buffered that is ready for processing */
+int
+camel_imapx_stream_buffered(CamelIMAPXStream *is)
+{
+ return is->end - is->ptr;
+}
+
+#if 0
+
+static int
+skip_ws(CamelIMAPXStream *is, unsigned char *pp, unsigned char *pe)
+{
+ register unsigned char c, *p;
+ unsigned char *e;
+
+ p = is->ptr;
+ e = is->end;
+
+ do {
+ while (p >= e ) {
+ is->ptr = p;
+ if (stream_fill(is) == IMAP_TOK_ERROR)
+ return IMAP_TOK_ERROR;
+ p = is->ptr;
+ e = is->end;
+ }
+ c = *p++;
+ } while (c == ' ' || c == '\r');
+
+ is->ptr = p;
+ is->end = e;
+
+ return c;
+}
+#endif
+
+/* FIXME: these should probably handle it themselves,
+ and get rid of the token interface? */
+int
+camel_imapx_stream_atom(CamelIMAPXStream *is, unsigned char **data, unsigned int *lenp)
+{
+ unsigned char *p, c;
+
+ /* this is only 'approximate' atom */
+ switch(camel_imapx_stream_token(is, data, lenp)) {
+ case IMAP_TOK_TOKEN:
+ p = *data;
+ while ((c = *p))
+ *p++ = toupper(c);
+ case IMAP_TOK_INT:
+ return 0;
+ case IMAP_TOK_ERROR:
+ return IMAP_TOK_ERROR;
+ default:
+ camel_exception_throw(1, "expecting atom");
+ printf("expecting atom!\n");
+ return IMAP_TOK_PROTOCOL;
+ }
+}
+
+/* gets an atom, a quoted_string, or a literal */
+int
+camel_imapx_stream_astring(CamelIMAPXStream *is, unsigned char **data)
+{
+ unsigned char *p, *start;
+ unsigned int len, inlen;
+
+ switch(camel_imapx_stream_token(is, data, &len)) {
+ case IMAP_TOK_TOKEN:
+ case IMAP_TOK_INT:
+ case IMAP_TOK_STRING:
+ return 0;
+ case IMAP_TOK_LITERAL:
+ /* FIXME: just grow buffer */
+ if (len >= CAMEL_IMAPX_STREAM_TOKEN) {
+ camel_exception_throw(1, "astring: literal too long");
+ printf("astring too long\n");
+ return IMAP_TOK_PROTOCOL;
+ }
+ p = is->tokenptr;
+ camel_imapx_stream_set_literal(is, len);
+ do {
+ len = camel_imapx_stream_getl(is, &start, &inlen);
+ if (len < 0)
+ return len;
+ memcpy(p, start, inlen);
+ p += inlen;
+ } while (len > 0);
+ *data = is->tokenptr;
+ return 0;
+ case IMAP_TOK_ERROR:
+ /* wont get unless no exception hanlder*/
+ return IMAP_TOK_ERROR;
+ default:
+ camel_exception_throw(1, "expecting astring");
+ printf("expecting astring!\n");
+ return IMAP_TOK_PROTOCOL;
+ }
+}
+
+/* check for NIL or (small) quoted_string or literal */
+int
+camel_imapx_stream_nstring(CamelIMAPXStream *is, unsigned char **data)
+{
+ unsigned char *p, *start;
+ unsigned int len, inlen;
+
+ switch(camel_imapx_stream_token(is, data, &len)) {
+ case IMAP_TOK_STRING:
+ return 0;
+ case IMAP_TOK_LITERAL:
+ /* FIXME: just grow buffer */
+ if (len >= CAMEL_IMAPX_STREAM_TOKEN) {
+ camel_exception_throw(1, "nstring: literal too long");
+ return IMAP_TOK_PROTOCOL;
+ }
+ p = is->tokenptr;
+ camel_imapx_stream_set_literal(is, len);
+ do {
+ len = camel_imapx_stream_getl(is, &start, &inlen);
+ if (len < 0)
+ return len;
+ memcpy(p, start, inlen);
+ p += inlen;
+ } while (len > 0);
+ *data = is->tokenptr;
+ return 0;
+ case IMAP_TOK_TOKEN:
+ p = *data;
+ if (toupper(p[0]) == 'N' && toupper(p[1]) == 'I' && toupper(p[2]) == 'L' && p[3] == 0) {
+ *data = NULL;
+ return 0;
+ }
+ default:
+ camel_exception_throw(1, "expecting nstring");
+ return IMAP_TOK_PROTOCOL;
+ case IMAP_TOK_ERROR:
+ /* we'll never get this unless there are no exception handlers anyway */
+ return IMAP_TOK_ERROR;
+
+ }
+}
+
+/* parse an nstring as a stream */
+int
+camel_imapx_stream_nstring_stream(CamelIMAPXStream *is, CamelStream **stream)
+/* throws IO,PARSE exception */
+{
+ unsigned char *token;
+ unsigned int len;
+ int ret = 0;
+ CamelStream * volatile mem = NULL;
+
+ *stream = NULL;
+
+ CAMEL_TRY {
+ switch(camel_imapx_stream_token(is, &token, &len)) {
+ case IMAP_TOK_STRING:
+ mem = camel_stream_mem_new_with_buffer(token, len);
+ *stream = mem;
+ break;
+ case IMAP_TOK_LITERAL:
+ /* if len is big, we could automatically use a file backing */
+ camel_imapx_stream_set_literal(is, len);
+ mem = camel_stream_mem_new();
+ if (camel_stream_write_to_stream((CamelStream *)is, mem) == -1)
+ camel_exception_throw(1, "nstring: io error: %s", strerror(errno));
+ camel_stream_reset(mem);
+ *stream = mem;
+ break;
+ case IMAP_TOK_TOKEN:
+ if (toupper(token[0]) == 'N' && toupper(token[1]) == 'I' && toupper(token[2]) == 'L' && token[3] == 0) {
+ *stream = NULL;
+ break;
+ }
+ default:
+ ret = -1;
+ camel_exception_throw(1, "nstring: token not string");
+ }
+ } CAMEL_CATCH(ex) {
+ if (mem)
+ camel_object_unref((CamelObject *)mem);
+ camel_exception_throw_ex(ex);
+ } CAMEL_DONE;
+
+ /* never reaches here anyway */
+ return ret;
+}
+
+guint32
+camel_imapx_stream_number(CamelIMAPXStream *is)
+{
+ unsigned char *token;
+ unsigned int len;
+
+ if (camel_imapx_stream_token(is, &token, &len) != IMAP_TOK_INT) {
+ camel_exception_throw(1, "expecting number");
+ return 0;
+ }
+
+ return strtoul(token, 0, 10);
+}
+
+int
+camel_imapx_stream_text(CamelIMAPXStream *is, unsigned char **text)
+{
+ GByteArray *build = g_byte_array_new();
+ unsigned char *token;
+ unsigned int len;
+ int tok;
+
+ CAMEL_TRY {
+ while (is->unget > 0) {
+ switch (is->unget_tok) {
+ case IMAP_TOK_TOKEN:
+ case IMAP_TOK_STRING:
+ case IMAP_TOK_INT:
+ g_byte_array_append(build, is->unget_token, is->unget_len);
+ g_byte_array_append(build, " ", 1);
+ default: /* invalid, but we'll ignore */
+ break;
+ }
+ is->unget--;
+ }
+
+ do {
+ tok = camel_imapx_stream_gets(is, &token, &len);
+ if (tok < 0)
+ camel_exception_throw(1, "io error: %s", strerror(errno));
+ if (len)
+ g_byte_array_append(build, token, len);
+ } while (tok > 0);
+ } CAMEL_CATCH(ex) {
+ *text = NULL;
+ g_byte_array_free(build, TRUE);
+ camel_exception_throw_ex(ex);
+ } CAMEL_DONE;
+
+ g_byte_array_append(build, "", 1);
+ *text = build->data;
+ g_byte_array_free(build, FALSE);
+
+ return 0;
+}
+
+/* Get one token from the imap stream */
+camel_imapx_token_t
+/* throws IO,PARSE exception */
+camel_imapx_stream_token(CamelIMAPXStream *is, unsigned char **data, unsigned int *len)
+{
+ register unsigned char c, *p, *o, *oe;
+ unsigned char *e;
+ unsigned int literal;
+ int digits;
+
+ if (is->unget > 0) {
+ is->unget--;
+ *data = is->unget_token;
+ *len = is->unget_len;
+ /*printf("token UNGET '%c' %s\n", is->unget_tok, is->unget_token);*/
+ return is->unget_tok;
+ }
+
+ if (is->literal > 0)
+ g_warning("stream_token called with literal %d", is->literal);
+
+ p = is->ptr;
+ e = is->end;
+
+ /* skip whitespace/prefill buffer */
+ do {
+ while (p >= e ) {
+ is->ptr = p;
+ if (stream_fill(is) == IMAP_TOK_ERROR)
+ goto io_error;
+ p = is->ptr;
+ e = is->end;
+ }
+ c = *p++;
+ } while (c == ' ' || c == '\r');
+
+ /*strchr("\n*()[]+", c)*/
+ if (imapx_is_token_char(c)) {
+ is->ptr = p;
+ t(printf("token '%c'\n", c));
+ return c;
+ } else if (c == '{') {
+ literal = 0;
+ *data = p;
+ while (1) {
+ while (p < e) {
+ c = *p++;
+ if (isdigit(c) && literal < (UINT_MAX/10)) {
+ literal = literal * 10 + (c - '0');
+ } else if (c == '}') {
+ while (1) {
+ while (p < e) {
+ c = *p++;
+ if (c == '\n') {
+ *len = literal;
+ is->ptr = p;
+ is->literal = literal;
+ t(printf("token LITERAL %d\n", literal));
+ return IMAP_TOK_LITERAL;
+ }
+ }
+ is->ptr = p;
+ if (stream_fill(is) == IMAP_TOK_ERROR)
+ goto io_error;
+ p = is->ptr;
+ e = is->end;
+ }
+ } else {
+ if (isdigit(c))
+ printf("Protocol error: literal too big\n");
+ else
+ printf("Protocol error: literal contains invalid char %02x '%c'\n", c, isprint(c)?c:c);
+ goto protocol_error;
+ }
+ }
+ is->ptr = p;
+ if (stream_fill(is) == IMAP_TOK_ERROR)
+ goto io_error;
+ p = is->ptr;
+ e = is->end;
+ }
+ } else if (c == '"') {
+ o = is->tokenptr;
+ oe = is->tokenptr + CAMEL_IMAPX_STREAM_TOKEN - 1;
+ while (1) {
+ while (p < e) {
+ c = *p++;
+ if (c == '\\') {
+ while (p >= e) {
+ is->ptr = p;
+ if (stream_fill(is) == IMAP_TOK_ERROR)
+ goto io_error;
+ p = is->ptr;
+ e = is->end;
+ }
+ c = *p++;
+ } else if (c == '\"') {
+ is->ptr = p;
+ *o = 0;
+ *data = is->tokenbuf;
+ *len = o - is->tokenbuf;
+ t(printf("token STRING '%s'\n", is->tokenbuf));
+ return IMAP_TOK_STRING;
+ }
+
+ if (c == '\n' || c == '\r' || o>=oe) {
+ if (o >= oe)
+ printf("Protocol error: string too long\n");
+ else
+ printf("Protocol error: truncated string\n");
+ goto protocol_error;
+ } else {
+ *o++ = c;
+ }
+ }
+ is->ptr = p;
+ if (stream_fill(is) == IMAP_TOK_ERROR)
+ goto io_error;
+ p = is->ptr;
+ e = is->end;
+ }
+ } else {
+ o = is->tokenptr;
+ oe = is->tokenptr + CAMEL_IMAPX_STREAM_TOKEN - 1;
+ digits = isdigit(c);
+ *o++ = c;
+ while (1) {
+ while (p < e) {
+ c = *p++;
+ /*if (strchr(" \r\n*()[]+", c) != NULL) {*/
+ if (imapx_is_notid_char(c)) {
+ if (c == ' ' || c == '\r')
+ is->ptr = p;
+ else
+ is->ptr = p-1;
+ *o = 0;
+ *data = is->tokenbuf;
+ *len = o - is->tokenbuf;
+ t(printf("token TOKEN '%s'\n", is->tokenbuf));
+ return digits?IMAP_TOK_INT:IMAP_TOK_TOKEN;
+ } else if (o < oe) {
+ digits &= isdigit(c);
+ *o++ = c;
+ } else {
+ printf("Protocol error: token too long\n");
+ goto protocol_error;
+ }
+ }
+ is->ptr = p;
+ if (stream_fill(is) == IMAP_TOK_ERROR)
+ goto io_error;
+ p = is->ptr;
+ e = is->end;
+ }
+ }
+
+ /* Had an i/o erorr */
+io_error:
+ printf("Got io error\n");
+ camel_exception_throw(1, "io error");
+ return IMAP_TOK_ERROR;
+
+ /* Protocol error, skip until next lf? */
+protocol_error:
+ printf("Got protocol error\n");
+
+ if (c == '\n')
+ is->ptr = p-1;
+ else
+ is->ptr = p;
+
+ camel_exception_throw(1, "protocol error");
+ return IMAP_TOK_PROTOCOL;
+}
+
+void
+camel_imapx_stream_ungettoken(CamelIMAPXStream *is, camel_imapx_token_t tok, unsigned char *token, unsigned int len)
+{
+ /*printf("ungettoken: '%c' '%s'\n", tok, token);*/
+ is->unget_tok = tok;
+ is->unget_token = token;
+ is->unget_len = len;
+ is->unget++;
+}
+
+/* returns -1 on error, 0 if last lot of data, >0 if more remaining */
+int camel_imapx_stream_gets(CamelIMAPXStream *is, unsigned char **start, unsigned int *len)
+{
+ int max;
+ unsigned char *end;
+
+ *len = 0;
+
+ max = is->end - is->ptr;
+ if (max == 0) {
+ max = stream_fill(is);
+ if (max <= 0)
+ return max;
+ }
+
+ *start = is->ptr;
+ end = memchr(is->ptr, '\n', max);
+ if (end)
+ max = (end - is->ptr) + 1;
+ *start = is->ptr;
+ *len = max;
+ is->ptr += max;
+
+ return end == NULL?1:0;
+}
+
+void camel_imapx_stream_set_literal(CamelIMAPXStream *is, unsigned int literal)
+{
+ is->literal = literal;
+}
+
+/* returns -1 on erorr, 0 if last data, >0 if more data left */
+int camel_imapx_stream_getl(CamelIMAPXStream *is, unsigned char **start, unsigned int *len)
+{
+ int max;
+
+ *len = 0;
+
+ if (is->literal > 0) {
+ max = is->end - is->ptr;
+ if (max == 0) {
+ max = stream_fill(is);
+ if (max <= 0)
+ return max;
+ }
+
+ max = MIN(max, is->literal);
+ *start = is->ptr;
+ *len = max;
+ is->ptr += max;
+ is->literal -= max;
+ }
+
+ if (is->literal > 0)
+ return 1;
+
+ return 0;
+}
+
+/* skip the rest of the line of tokens */
+int
+camel_imapx_stream_skip(CamelIMAPXStream *is)
+{
+ int tok;
+ unsigned char *token;
+ unsigned int len;
+
+ do {
+ tok = camel_imapx_stream_token(is, &token, &len);
+ if (tok == IMAP_TOK_LITERAL) {
+ camel_imapx_stream_set_literal(is, len);
+ while ((tok = camel_imapx_stream_getl(is, &token, &len)) > 0) {
+ printf("Skip literal data '%.*s'\n", (int)len, token);
+ }
+ }
+ } while (tok != '\n' && tok >= 0);
+
+ if (tok < 0)
+ return -1;
+
+ return 0;
+}
diff --git a/camel/providers/imap/camel-imapx-stream.h b/camel/providers/imap/camel-imapx-stream.h
new file mode 100644
index 000000000..b565b4ccd
--- /dev/null
+++ b/camel/providers/imap/camel-imapx-stream.h
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2000 Ximian Inc.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU General Public
+ * License as published by the Free Software Foundation.
+ *
+ * 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 Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef _CAMEL_IMAPX_STREAM_H
+#define _CAMEL_IMAPX_STREAM_H
+
+#include <camel/camel-stream.h>
+
+#define CAMEL_IMAPX_STREAM(obj) CAMEL_CHECK_CAST (obj, camel_imapx_stream_get_type (), CamelIMAPXStream)
+#define CAMEL_IMAPX_STREAM_CLASS(klass) CAMEL_CHECK_CLASS_CAST (klass, camel_imapx_stream_get_type (), CamelIMAPXStreamClass)
+#define CAMEL_IS_IMAPX_STREAM(obj) CAMEL_CHECK_TYPE (obj, camel_imapx_stream_get_type ())
+
+typedef struct _CamelIMAPXStreamClass CamelIMAPXStreamClass;
+typedef struct _CamelIMAPXStream CamelIMAPXStream;
+
+typedef enum {
+ IMAP_TOK_PROTOCOL = -2,
+ IMAP_TOK_ERROR = -1,
+ IMAP_TOK_TOKEN = 256,
+ IMAP_TOK_STRING,
+ IMAP_TOK_INT,
+ IMAP_TOK_LITERAL,
+} camel_imapx_token_t;
+
+struct _CamelIMAPXStream {
+ CamelStream parent;
+
+ CamelStream *source;
+
+ /*int state;*/
+ unsigned char *buf, *ptr, *end;
+ unsigned int literal;
+
+ unsigned int unget;
+ camel_imapx_token_t unget_tok;
+ unsigned char *unget_token;
+ unsigned int unget_len;
+
+ unsigned char *tokenbuf, *tokenptr, *tokenend;
+};
+
+struct _CamelIMAPXStreamClass {
+ CamelStreamClass parent_class;
+};
+
+CamelType camel_imapx_stream_get_type (void);
+
+CamelStream *camel_imapx_stream_new (CamelStream *source);
+
+int camel_imapx_stream_buffered (CamelIMAPXStream *is);
+
+camel_imapx_token_t camel_imapx_stream_token (CamelIMAPXStream *is, unsigned char **start, unsigned int *len); /* throws IO,PARSE exception */
+void camel_imapx_stream_ungettoken (CamelIMAPXStream *is, camel_imapx_token_t tok, unsigned char *token, unsigned int len);
+
+void camel_imapx_stream_set_literal (CamelIMAPXStream *is, unsigned int literal);
+int camel_imapx_stream_gets (CamelIMAPXStream *is, unsigned char **start, unsigned int *len);
+int camel_imapx_stream_getl (CamelIMAPXStream *is, unsigned char **start, unsigned int *len);
+
+/* all throw IO,PARSE exceptions */
+
+/* gets an atom, upper-cases */
+int camel_imapx_stream_atom (CamelIMAPXStream *is, unsigned char **start, unsigned int *len);
+/* gets an atom or string */
+int camel_imapx_stream_astring (CamelIMAPXStream *is, unsigned char **start);
+/* gets a NIL or a string, start==NULL if NIL */
+int camel_imapx_stream_nstring (CamelIMAPXStream *is, unsigned char **start);
+/* gets a NIL or string into a stream, stream==NULL if NIL */
+int camel_imapx_stream_nstring_stream(CamelIMAPXStream *is, CamelStream **stream);
+/* gets 'text' */
+int camel_imapx_stream_text (CamelIMAPXStream *is, unsigned char **text);
+
+/* gets a 'number' */
+guint32 camel_imapx_stream_number(CamelIMAPXStream *is);
+
+/* skips the rest of a line, including literals, etc */
+int camel_imapx_stream_skip(CamelIMAPXStream *is);
+
+#endif /* ! _CAMEL_IMAPX_STREAM_H */