diff options
author | Chenthill Palanisamy <pchenthill@novell.com> | 2009-08-20 13:41:15 +0530 |
---|---|---|
committer | Chenthill Palanisamy <pchenthill@novell.com> | 2009-08-20 13:58:08 +0530 |
commit | d7316282f8150c11f0f2a82570430221829d4eb3 (patch) | |
tree | e77e7f43653c17aa78ccb06e6c193d574b333ec6 | |
parent | d603cd4538b3a28c837a17a893c090b482a8d0c0 (diff) | |
download | evolution-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.c | 2818 | ||||
-rw-r--r-- | camel/providers/imap/camel-imapx-server.h | 105 | ||||
-rw-r--r-- | camel/providers/imap/camel-imapx-stream.c | 728 | ||||
-rw-r--r-- | camel/providers/imap/camel-imapx-stream.h | 95 |
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 */ |