diff options
22 files changed, 7458 insertions, 0 deletions
diff --git a/camel/providers/imapp/Makefile.am b/camel/providers/imapp/Makefile.am new file mode 100644 index 0000000000..06ce49dd7b --- /dev/null +++ b/camel/providers/imapp/Makefile.am @@ -0,0 +1,46 @@ +## Process this file with automake to produce Makefile.in + +libcamelimappincludedir = $(privincludedir)/camel + +camel_provider_LTLIBRARIES = libcamelimapp.la +camel_provider_DATA = libcamelimapp.urls + +INCLUDES = -I.. \ + -I$(srcdir)/.. \ + -I$(top_srcdir)/camel \ + -I$(top_srcdir)/intl \ + -I$(top_srcdir)/e-util \ + -I$(top_srcdir) \ + $(CAMEL_CFLAGS) \ + $(GNOME_INCLUDEDIR) \ + $(GTK_INCLUDEDIR) \ + -DG_LOG_DOMAIN=\"camel-imapp-provider\" \ + -DG_DISABLE_DEPRECATED + +libcamelimapp_la_SOURCES = \ + camel-imapp-utils.c \ + camel-imapp-engine.c \ + camel-imapp-stream.c \ + camel-imapp-store.c \ + camel-imapp-folder.c \ + camel-imapp-provider.c \ + camel-imapp-store-summary.c \ + camel-imapp-driver.c \ + camel-imapp-summary.c + +libcamelimappinclude_HEADERS = \ + camel-imapp-utils.h \ + camel-imapp-engine.h \ + camel-imapp-stream.h \ + camel-imapp-store.h \ + camel-imapp-folder.h \ + camel-imapp-store-summary.h \ + camel-imapp-driver.h \ + camel-imapp-summary.h + +libcamelimapp_la_LDFLAGS = -avoid-version -module + +#noinst_HEADERS = \ +# camel-imap-private.h + +EXTRA_DIST = libcamelimapp.urls diff --git a/camel/providers/imapp/camel-imapp-driver.c b/camel/providers/imapp/camel-imapp-driver.c new file mode 100644 index 0000000000..f9daced770 --- /dev/null +++ b/camel/providers/imapp/camel-imapp-driver.c @@ -0,0 +1,748 @@ + + +#include "config.h" + +#include <stdlib.h> +#include <stdio.h> +#include <ctype.h> + +#include "camel-imapp-driver.h" +#include "camel-imapp-utils.h" +#include "camel-imapp-folder.h" +#include "camel-imapp-engine.h" +#include "camel-imapp-summary.h" +#include "camel-imapp-exception.h" + +#include <camel/camel-stream-mem.h> +#include <camel/camel-stream-null.h> + +#include <camel/camel-folder-summary.h> +#include <camel/camel-store.h> +#include <camel/camel-mime-utils.h> +#include <camel/camel-sasl.h> + +#define d(x) x + +static int driver_resp_fetch(CamelIMAPPEngine *ie, guint32 id, CamelIMAPPDriver *sdata); +static int driver_resp_expunge(CamelIMAPPEngine *ie, guint32 id, CamelIMAPPDriver *sdata); +static int driver_resp_exists(CamelIMAPPEngine *ie, guint32 id, CamelIMAPPDriver *sdata); +static int driver_resp_list(CamelIMAPPEngine *ie, guint32 id, CamelIMAPPDriver *sdata); + +static void driver_status(CamelIMAPPEngine *ie, struct _status_info *sinfo, CamelIMAPPDriver *sdata); + +static void +class_init(CamelIMAPPDriverClass *ieclass) +{ +} + +static void +object_init(CamelIMAPPDriver *ie, CamelIMAPPDriverClass *ieclass) +{ + ie->summary = g_ptr_array_new(); + e_dlist_init(&ie->body_fetch); + e_dlist_init(&ie->body_fetch_done); +} + +static void +object_finalise(CamelIMAPPDriver *ie, CamelIMAPPDriverClass *ieclass) +{ + if (ie->folder) + camel_object_unref((CamelObject *)ie->folder); + if (ie->engine) + camel_object_unref((CamelObject *)ie->engine); + if (ie->summary) + g_ptr_array_free(ie->summary, TRUE); +} + +CamelType +camel_imapp_driver_get_type (void) +{ + static CamelType type = CAMEL_INVALID_TYPE; + + if (type == CAMEL_INVALID_TYPE) { + type = camel_type_register ( + camel_object_get_type (), + "CamelIMAPPDriver", + sizeof (CamelIMAPPDriver), + sizeof (CamelIMAPPDriverClass), + (CamelObjectClassInitFunc) class_init, + NULL, + (CamelObjectInitFunc) object_init, + (CamelObjectFinalizeFunc) object_finalise); + + /* blah ... could just use it in object setup? */ + /* TEMPORARY */ + camel_exception_setup(); + } + + return type; +} + +CamelIMAPPDriver * +camel_imapp_driver_new(CamelIMAPPStream *stream) +{ + CamelIMAPPDriver *driver; + CamelIMAPPEngine *ie; + + driver = CAMEL_IMAPP_DRIVER (camel_object_new (CAMEL_IMAPP_DRIVER_TYPE)); + ie = driver->engine = camel_imapp_engine_new(stream); + + camel_imapp_engine_add_handler(ie, "FETCH", (CamelIMAPPEngineFunc)driver_resp_fetch, driver); + camel_imapp_engine_add_handler(ie, "EXPUNGE", (CamelIMAPPEngineFunc)driver_resp_expunge, driver); + camel_imapp_engine_add_handler(ie, "EXISTS", (CamelIMAPPEngineFunc)driver_resp_exists, driver); + camel_imapp_engine_add_handler(ie, "LIST", (CamelIMAPPEngineFunc)driver_resp_list, driver); + camel_object_hook_event(ie, "status", (CamelObjectEventHookFunc)driver_status, driver); + + return driver; +} + +void +camel_imapp_driver_set_sasl_factory(CamelIMAPPDriver *id, CamelIMAPPSASLFunc get_sasl, void *sasl_data) +{ + id->get_sasl = get_sasl; + id->get_sasl_data = sasl_data; +} + +void +camel_imapp_driver_set_login_query(CamelIMAPPDriver *id, CamelIMAPPLoginFunc get_login, void *login_data) +{ + id->get_login = get_login; + id->get_login_data = login_data; +} + +void +camel_imapp_driver_login(CamelIMAPPDriver *id) +/* throws SERVICE_CANT_AUTHENTICATE, SYSTEM_IO */ +{ + CamelIMAPPCommand * volatile ic = NULL; + + /* connect? */ + /* camel_imapp_engine_connect() */ + /* or above? */ + + CAMEL_TRY { + CamelSasl *sasl; + + if (id->get_sasl + && (sasl = id->get_sasl(id, id->get_sasl_data))) { + ic = camel_imapp_engine_command_new(id->engine, "AUTHENTICATE", NULL, "AUTHENTICATE %A", sasl); + camel_object_unref(sasl); + } else { + char *user, *pass; + + g_assert(id->get_login); + id->get_login(id, &user, &pass, id->get_login_data); + ic = camel_imapp_engine_command_new(id->engine, "LOGIN", NULL, "LOGIN %s %s", user, pass); + g_free(user); + g_free(pass); + } + + camel_imapp_engine_command_queue(id->engine, ic); + while (camel_imapp_engine_iterate(id->engine, ic) > 0) + ; + + if (ic->status->result != IMAP_OK) + camel_exception_throw(CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE, "Login failed: %s", ic->status->text); + camel_imapp_engine_command_free(id->engine, ic); + } CAMEL_CATCH(ex) { + if (ic) + camel_imapp_engine_command_free(id->engine, ic); + camel_exception_throw_ex(ex); + } CAMEL_DONE; +} + +void +camel_imapp_driver_select(CamelIMAPPDriver *id, struct _CamelIMAPPFolder *folder) +{ + CamelIMAPPSelectResponse *sr; + CamelIMAPPCommand * volatile ic = NULL; + CamelIMAPPCommand * volatile ic2 = NULL; + guint32 count; + CamelFolderSummary *summary; + + if (id->folder) { + if (folder == id->folder) + return; + camel_imapp_driver_sync(id, FALSE, id->folder); + if (camel_folder_change_info_changed(id->folder->changes)) { + camel_object_trigger_event(id->folder, "folder_changed", id->folder->changes); + camel_folder_change_info_clear(id->folder->changes); + } + camel_object_unref(id->folder); + id->folder = NULL; + } + + summary = ((CamelFolder *)folder)->summary; + + ic = camel_imapp_engine_command_new(id->engine, "SELECT", NULL, "SELECT %t", folder->raw_name); + camel_imapp_engine_command_queue(id->engine, ic); + while (camel_imapp_engine_iterate(id->engine, ic)>0) + ; + camel_imapp_engine_command_free(id->engine, ic); + + id->folder = folder; + camel_object_ref(folder); + + count = camel_folder_summary_count(summary); + if (count > 0 && count <= id->exists) { + ic = camel_imapp_engine_command_new(id->engine, "FETCH", NULL, + "FETCH 1:%u (UID FLAGS)", count); + camel_imapp_engine_command_queue(id->engine, ic); + if (count < id->exists) { + ic2 = camel_imapp_engine_command_new(id->engine, "FETCH", NULL, + "FETCH %u:* (UID FLAGS ENVELOPE)", count+1); + camel_imapp_engine_command_queue(id->engine, ic2); + } else { + ic2 = NULL; + } + + while (camel_imapp_engine_iterate(id->engine, ic2?ic2:ic)>0) + ; + + camel_imapp_engine_command_free(id->engine, ic); + if (ic2) + camel_imapp_engine_command_free(id->engine, ic2); + } else { + ic = camel_imapp_engine_command_new(id->engine, "FETCH", NULL, + "FETCH 1:* (UID FLAGS ENVELOPE)"); + camel_imapp_engine_command_queue(id->engine, ic); + while (camel_imapp_engine_iterate(id->engine, ic)>0) + ; + camel_imapp_engine_command_free(id->engine, ic); + } + + /* TODO: need to set exists/etc in summary */ + folder->exists = id->exists; + folder->uidvalidity = id->uidvalidity; + + printf("saving summary '%s'\n", summary->summary_path); + camel_folder_summary_save(summary); + + if (camel_folder_change_info_changed(id->folder->changes)) { + camel_object_trigger_event(id->folder, "folder_changed", id->folder->changes); + camel_folder_change_info_clear(id->folder->changes); + } +} + +static void +imapp_driver_check(CamelIMAPPDriver *id) +{ + guint32 count; + CamelIMAPPCommand *ic; + + /* FIXME: exception handling */ + + if (id->folder->exists != id->exists) { + count = camel_folder_summary_count(((CamelFolder *)id->folder)->summary); + if (count < id->exists) { + printf("fetching new messages\n"); + ic = camel_imapp_engine_command_new(id->engine, "FETCH", NULL, + "FETCH %u:* (UID FLAGS ENVELOPE)", count+1); + camel_imapp_engine_command_queue(id->engine, ic); + while (camel_imapp_engine_iterate(id->engine, ic)>0) + ; + camel_imapp_engine_command_free(id->engine, ic); + } else if (count > id->exists) { + printf("folder shrank with no expunge notificaitons!? uh, dunno what to do\n"); + } + } + + printf("checking for change info changes\n"); + if (camel_folder_change_info_changed(id->folder->changes)) { + printf("got somechanges! added=%d changed=%d removed=%d\n", + id->folder->changes->uid_added->len, + id->folder->changes->uid_changed->len, + id->folder->changes->uid_removed->len); + camel_object_trigger_event(id->folder, "folder_changed", id->folder->changes); + camel_folder_change_info_clear(id->folder->changes); + } +} + +void +camel_imapp_driver_update(CamelIMAPPDriver *id, CamelIMAPPFolder *folder) +{ + if (id->folder == folder) { + CamelIMAPPCommand *ic; + + /* this will automagically update flags & expunge items */ + ic = camel_imapp_engine_command_new(id->engine, "NOOP", NULL, "NOOP"); + camel_imapp_engine_command_queue(id->engine, ic); + while (camel_imapp_engine_iterate(id->engine, ic)>0) + ; + camel_imapp_engine_command_free(id->engine, ic); + + imapp_driver_check(id); + } else { + camel_imapp_driver_select(id, folder); + } +} + +/* FIXME: this is basically a copy of the same in camel-imapp-utils.c */ +static struct { + char *name; + guint32 flag; +} flag_table[] = { + { "\\ANSWERED", CAMEL_MESSAGE_ANSWERED }, + { "\\DELETED", CAMEL_MESSAGE_DELETED }, + { "\\DRAFT", CAMEL_MESSAGE_DRAFT }, + { "\\FLAGGED", CAMEL_MESSAGE_FLAGGED }, + { "\\SEEN", CAMEL_MESSAGE_SEEN }, + /* { "\\RECENT", CAMEL_IMAPP_MESSAGE_RECENT }, */ +}; + +/* + flags 00101000 + sflags 01001000 + ^ 01100000 +~flags 11010111 +& 01000000 + +&flags 00100000 +*/ + +static void +imapp_write_flags(CamelIMAPPDriver *id, guint32 orset, gboolean on, CamelFolderSummary *summary) +{ + guint32 i, j, count; + CamelIMAPPMessageInfo *info; + CamelIMAPPCommand *ic = NULL; + struct _uidset_state ss; + GSList *commands = NULL; + + /* FIXME: exception handling */ + + count = camel_folder_summary_count(summary); + for (j=0;j<sizeof(flag_table)/sizeof(flag_table[0]);j++) { + int flush; + + if ((orset & flag_table[j].flag) == 0) + continue; + + printf("checking/storing %s flags '%s'\n", on?"on":"off", flag_table[j].name); + + flush = 0; + imapp_uidset_init(&ss, id->engine); + for (i=0;i<count;i++) { + info = (CamelIMAPPMessageInfo *)camel_folder_summary_index(summary, i); + if (info) { + guint32 flags = info->info.flags & CAMEL_IMAPP_SERVER_FLAGS; + guint32 sflags = info->server_flags & CAMEL_IMAPP_SERVER_FLAGS; + + if ( (on && (((flags ^ sflags) & flags) & flag_table[j].flag)) + || (!on && (((flags ^ sflags) & ~flags) & flag_table[j].flag))) { + if (ic == NULL) + ic = camel_imapp_engine_command_new(id->engine, "STORE", NULL, "UID STORE "); + flush = imapp_uidset_add(&ss, ic, camel_message_info_uid(info)); + } + camel_folder_summary_info_free(summary, (CamelMessageInfo *)info); + } + + if (i == count-1 && ic != NULL) + flush |= imapp_uidset_done(&ss, ic); + + if (flush) { + flush = 0; + camel_imapp_engine_command_add(id->engine, ic, " %tFLAGS.SILENT (%t)", on?"+":"-", flag_table[j].name); + camel_imapp_engine_command_queue(id->engine, ic); + commands = g_slist_prepend(commands, ic); + ic = NULL; + } + } + } + + /* flush off any requests we may have outstanding */ + /* TODO: for max benefit, should have this routine do both on and off flags in one go */ + while (commands) { + GSList *next = commands->next; + + ic = commands->data; + g_slist_free_1(commands); + commands = next; + + while (camel_imapp_engine_iterate(id->engine, ic)>0) + ; + camel_imapp_engine_command_free(id->engine, ic); + } +} + +void +camel_imapp_driver_sync(CamelIMAPPDriver *id, gboolean expunge, CamelIMAPPFolder *folder) +{ + CamelFolderSummary *summary; + guint i, count, on_orset, off_orset; + CamelIMAPPMessageInfo *info; + CamelIMAPPCommand *ic; + + /* FIXME: exception handling */ + + camel_imapp_driver_update(id, folder); + + summary = ((CamelFolder *)folder)->summary; + count = camel_folder_summary_count(summary); + /* find out which flags have turned on, which have tunred off */ + off_orset = on_orset = 0; + for (i=0;i<count;i++) { + guint32 flags, sflags; + + info = (CamelIMAPPMessageInfo *)camel_folder_summary_index(summary, i); + if (info == NULL) + continue; + flags = info->info.flags & CAMEL_IMAPP_SERVER_FLAGS; + sflags = info->server_flags & CAMEL_IMAPP_SERVER_FLAGS; + if (flags != sflags) { + off_orset |= ( flags ^ sflags ) & ~flags; + on_orset |= (flags ^ sflags) & flags; + } + camel_folder_summary_info_free(summary, (CamelMessageInfo *)info); + } + + if (on_orset || off_orset) { + /* turn on or off all messages matching */ + if (on_orset) + imapp_write_flags(id, on_orset, TRUE, summary); + if (off_orset) + imapp_write_flags(id, off_orset, FALSE, summary); + + /* success (no exception), make sure we match what we're supposed to */ + for (i=0;i<count;i++) { + guint32 flags, sflags; + + info = (CamelIMAPPMessageInfo *)camel_folder_summary_index(summary, i); + if (info == NULL) + continue; + info->server_flags = info->info.flags & CAMEL_IMAPP_SERVER_FLAGS; + camel_folder_summary_info_free(summary, (CamelMessageInfo *)info); + } + camel_folder_summary_touch(summary); + /* could save summary here, incase of failure? */ + } + + if (expunge) { + ic = camel_imapp_engine_command_new(id->engine, "EXPUNGE", NULL, "EXPUNGE"); + camel_imapp_engine_command_queue(id->engine, ic); + while (camel_imapp_engine_iterate(id->engine, ic)>0) + ; + camel_imapp_engine_command_free(id->engine, ic); + } + + printf("saving summary '%s'\n", summary->summary_path); + camel_folder_summary_save(summary); + + if (camel_folder_change_info_changed(id->folder->changes)) { + camel_object_trigger_event(id->folder, "folder_changed", id->folder->changes); + camel_folder_change_info_clear(id->folder->changes); + } +} + +struct _fetch_data { + struct _fetch_data *next; + struct _fetch_data *prev; + + CamelStream *data; + const char *uid; + const char *section; +}; + +CamelStream * +camel_imapp_driver_fetch(CamelIMAPPDriver *id, CamelIMAPPFolder *folder, const char *uid, const char *section) +{ + struct _fetch_data fd; + CamelIMAPPCommand *ic; + + fd.data = NULL; + fd.uid = uid; + fd.section = section; + e_dlist_addtail(&id->body_fetch, (EDListNode *)&fd); + + CAMEL_TRY { + camel_imapp_driver_select(id, folder); + + ic = camel_imapp_engine_command_new(id->engine, "FETCH", NULL, "UID FETCH %t (BODY.PEEK[%t])", uid, section); + camel_imapp_engine_command_queue(id->engine, ic); + while (camel_imapp_engine_iterate(id->engine, ic)>0) + ; + camel_imapp_engine_command_free(id->engine, ic); + imapp_driver_check(id); + } CAMEL_CATCH(e) { + /* FIXME: do exception properly */ + } CAMEL_DONE; + + e_dlist_remove((EDListNode *)&fd); + + return fd.data; +} + +GPtrArray * +camel_imapp_driver_list(CamelIMAPPDriver *id, const char *name, guint32 flags) +{ + CamelIMAPPCommand *ic; + GPtrArray *res; + + g_assert(id->list_commands == NULL); + g_assert(id->list_result == NULL); + + /* FIXME: make sure we only have a single list running at a time */ + /* sem_wait(id->list_sem); */ + + /* FIXME: namespace stuff (done in store code?) */ + + /* FIXME: if name != "", we need to also do list "name.%" (. == sep) */ + + id->list_result = g_ptr_array_new(); + id->list_flags = flags; + ic = camel_imapp_engine_command_new(id->engine, "LIST", NULL, "LIST \"\" %f", name[0]?name:"%"); + camel_imapp_engine_command_queue(id->engine, ic); + while (ic) { + while (camel_imapp_engine_iterate(id->engine, ic)>0) + ; + camel_imapp_engine_command_free(id->engine, ic); + + if (id->list_commands) { + GSList *top = id->list_commands; + + id->list_commands = top->next; + ic = top->data; + g_slist_free_1(top); + } else { + ic = NULL; + } + } + + res = id->list_result; + id->list_result = NULL; + + /* sem_post(id->list_sem); */ + + return res; +} + +static int +driver_resp_list(CamelIMAPPEngine *ie, guint32 idx, CamelIMAPPDriver *id) +{ + struct _list_info *linfo; + + /* FIXME: exceptions */ + + linfo = imap_parse_list(ie->stream); + printf("store list: '%s' ('%c')\n", linfo->name, linfo->separator); + if (id->list_result) { + if ((linfo->flags & CAMEL_FOLDER_NOINFERIORS) == 0 + && (id->list_flags & CAMEL_STORE_FOLDER_INFO_RECURSIVE) + && linfo->separator) { + int depth = 0; + char *p = linfo->name; + char c = linfo->separator; + + /* this is expensive ... but if we've listed this deep we're going slow anyway */ + while (*p && depth < 10) { + if (*p == c) + depth++; + p++; + } + + if (depth < 10 + && (linfo->name[0] == 0 || linfo->name[strlen(linfo->name)-1] != c)) { + CamelIMAPPCommand *ic; + + ic = camel_imapp_engine_command_new(id->engine, "LIST", NULL, "LIST \"\" %t%c%%", linfo->name, c); + id->list_commands = g_slist_prepend(id->list_commands, ic); + camel_imapp_engine_command_queue(id->engine, ic); + } + } + /* FIXME: dont add to list if name ends in separator */ + g_ptr_array_add(id->list_result, linfo); + } else { + g_warning("unexpected list response\n"); + imap_free_list(linfo); + } + + return camel_imapp_engine_skip(ie); +} + +/* ********************************************************************** */ + +static void +driver_status(CamelIMAPPEngine *ie, struct _status_info *sinfo, CamelIMAPPDriver *sdata) +{ + printf("got status response ...\n"); + switch(sinfo->condition) { + case IMAP_READ_WRITE: + printf("folder is read-write\n"); + break; + case IMAP_READ_ONLY: + printf("folder is read-only\n"); + break; + case IMAP_UIDVALIDITY: + sdata->uidvalidity = sinfo->u.uidvalidity; + break; +#if 0 + /* not defined yet ... */ + case IMAP_UIDNEXT: + printf("got uidnext for folder: %d\n", sinfo->u.uidnext); + break; +#endif + case IMAP_UNSEEN: + sdata->unseen = sinfo->u.unseen; + break; + case IMAP_PERMANENTFLAGS: + sdata->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; + } +} + +static int +driver_resp_exists(CamelIMAPPEngine *ie, guint32 id, CamelIMAPPDriver *sdata) +{ + /* should this be an event instead? */ + + sdata->exists = id; + + return camel_imapp_engine_skip(ie); +} + +static int +driver_resp_expunge(CamelIMAPPEngine *ie, guint32 id, CamelIMAPPDriver *sdata) +{ + printf("got expunge response %u\n", id); + if (sdata->folder != NULL) { + CamelMessageInfo *info; + CamelFolderSummary *summary = ((CamelFolder *)sdata->folder)->summary; + + info = camel_folder_summary_index(summary, id-1); + if (info) { + printf("expunging msg %d\n", id); + camel_folder_summary_remove(summary, info); + camel_folder_summary_info_free(summary, info); + camel_folder_change_info_remove_uid(sdata->folder->changes, camel_message_info_uid(info)); + } else { + printf("can not find msg %u from expunge\n", id); + } + } + + return camel_imapp_engine_skip(ie); +} + +static int +driver_resp_fetch(CamelIMAPPEngine *ie, guint32 id, CamelIMAPPDriver *sdata) +{ + struct _fetch_info *finfo = NULL; + CamelMessageInfo *info, *uinfo; + unsigned int i; + CamelFolderSummary *summary; + + printf("got fetch response %d\n", id); + + if (sdata->folder == NULL) + goto done; + + summary = ((CamelFolder *)sdata->folder)->summary; + + finfo = imap_parse_fetch(ie->stream); + imap_dump_fetch(finfo); + + info = camel_folder_summary_index(summary, id-1); + if (info == NULL) { + if (finfo->uid == NULL) { + printf("got fetch response for currently unknown message %u\n", id); + goto done; + } + uinfo = camel_folder_summary_uid(summary, finfo->uid); + if (uinfo) { + /* we have a problem ... index mismatch */ + printf("index mismatch, uid '%s' not at index '%u'\n", + finfo->uid, id); + camel_folder_summary_info_free(summary, uinfo); + } + /* pad out the summary till we have enough indexes */ + for (i=camel_folder_summary_count(summary);i<id;i++) { + info = camel_folder_summary_info_new(summary); + if (i == id-1) { + printf("inserting new info @ %u\n", i); + camel_message_info_set_uid(info, g_strdup(finfo->uid)); + } else { + char uidtmp[32]; + + sprintf(uidtmp, "blank-%u", i); + camel_message_info_set_uid(info, g_strdup(uidtmp)); + printf("inserting empty uid %s\n", uidtmp); + } + + camel_folder_summary_add(summary, info); + } + info = camel_folder_summary_index(summary, id-1); + g_assert(info != NULL); + } else { + if (finfo->uid) { + /* FIXME: need to handle blank-* uids, somehow */ + while (info && strcmp(camel_message_info_uid(info), finfo->uid) != 0) { + printf("index mismatch, uid '%s' not at index '%u', got '%s' instead (removing)\n", + finfo->uid, id, camel_message_info_uid(info)); + + camel_folder_change_info_remove_uid(sdata->folder->changes, camel_message_info_uid(info)); + camel_folder_summary_remove(summary, info); + camel_folder_summary_info_free(summary, info); + info = camel_folder_summary_index(summary, id-1); + } + } else { + printf("got info for unknown message %u\n", id); + } + } + + if (info) { + if (finfo->got & FETCH_MINFO) { + /* if we only use ENVELOPE? */ + camel_message_info_set_subject(info, g_strdup(camel_message_info_subject(finfo->minfo))); + camel_message_info_set_from(info, g_strdup(camel_message_info_from(finfo->minfo))); + camel_message_info_set_to(info, g_strdup(camel_message_info_to(finfo->minfo))); + camel_message_info_set_cc(info, g_strdup(camel_message_info_cc(finfo->minfo))); + info->date_sent = finfo->minfo->date_sent; + camel_folder_change_info_add_uid(sdata->folder->changes, camel_message_info_uid(info)); + printf("adding change info uid '%s'\n", camel_message_info_uid(info)); + } + + if (finfo->got & FETCH_FLAGS) { + if ((info->flags & CAMEL_IMAPP_SERVER_FLAGS) != (finfo->flags & CAMEL_IMAPP_SERVER_FLAGS)) { + camel_folder_change_info_change_uid(sdata->folder->changes, camel_message_info_uid(info)); + info->flags = (info->flags & ~(CAMEL_IMAPP_SERVER_FLAGS)) | (finfo->flags & CAMEL_IMAPP_SERVER_FLAGS); + camel_folder_summary_touch(summary); + } + ((CamelIMAPPMessageInfo *)info)->server_flags = finfo->flags & CAMEL_IMAPP_SERVER_FLAGS; + } + + if ((finfo->got & (FETCH_BODY|FETCH_UID)) == (FETCH_BODY|FETCH_UID)) { + struct _fetch_data *fd, *fn; + + fd = (struct _fetch_data *)sdata->body_fetch.head; + fn = fd->next; + while (fn) { + if (!strcmp(finfo->uid, fd->uid) && !strcmp(finfo->section, fd->section)) { + if (fd->data) + camel_object_unref(fd->data); + fd->data = finfo->body; + camel_object_ref(fd->data); + e_dlist_remove((EDListNode *)fd); + e_dlist_addtail(&sdata->body_fetch_done, (EDListNode *)fd); + break; + } + fd = fn; + fn = fn->next; + } + } + + camel_folder_summary_info_free(summary, info); + } else { + printf("dont know what to do with message\n"); + } + done: + imap_free_fetch(finfo); + + return camel_imapp_engine_skip(ie); +} diff --git a/camel/providers/imapp/camel-imapp-driver.h b/camel/providers/imapp/camel-imapp-driver.h new file mode 100644 index 0000000000..3124700619 --- /dev/null +++ b/camel/providers/imapp/camel-imapp-driver.h @@ -0,0 +1,81 @@ + +#ifndef _CAMEL_IMAPP_DRIVER_H +#define _CAMEL_IMAPP_DRIVER_H + +#include <camel/camel-object.h> +#include "camel-imapp-stream.h" +#include <e-util/e-msgport.h> + +#define CAMEL_IMAPP_DRIVER_TYPE (camel_imapp_driver_get_type ()) +#define CAMEL_IMAPP_DRIVER(obj) (CAMEL_CHECK_CAST((obj), CAMEL_IMAPP_DRIVER_TYPE, CamelIMAPPDriver)) +#define CAMEL_IMAPP_DRIVER_CLASS(k) (CAMEL_CHECK_CLASS_CAST ((k), CAMEL_IMAPP_DRIVER_TYPE, CamelIMAPPDriverClass)) +#define CAMEL_IS_IMAP_DRIVER(o) (CAMEL_CHECK_TYPE((o), CAMEL_IMAPP_DRIVER_TYPE)) + +typedef struct _CamelIMAPPDriver CamelIMAPPDriver; +typedef struct _CamelIMAPPDriverClass CamelIMAPPDriverClass; + +typedef int (*CamelIMAPPDriverFunc)(struct _CamelIMAPPDriver *driver, void *data); +typedef struct _CamelSasl * (*CamelIMAPPSASLFunc)(struct _CamelIMAPPDriver *driver, void *data); +typedef void (*CamelIMAPPLoginFunc)(struct _CamelIMAPPDriver *driver, char **login, char **pass, void *data); + +struct _CamelMimeMessage; + +struct _CamelIMAPPDriver { + CamelObject parent_object; + + struct _CamelIMAPPEngine *engine; + + struct _CamelIMAPPFolder *folder; + + /* current folder stuff */ + GPtrArray *summary; + guint32 uidvalidity; + guint32 exists; + guint32 recent; + guint32 unseen; + guint32 permanentflags; + + /* list stuff */ + GPtrArray *list_result; + GSList *list_commands; + guint32 list_flags; + + /* sem_t list_sem; for controlled access to list variables */ + + /* this is so the node is always in a list - easier exception management */ + EDList body_fetch; + EDList body_fetch_done; + + /* factory to get an appropriate sasl mech */ + CamelIMAPPSASLFunc get_sasl; + void *get_sasl_data; + + /* callbacks, get login username/pass */ + CamelIMAPPLoginFunc get_login; + void *get_login_data; +}; + +struct _CamelIMAPPDriverClass { + CamelObjectClass parent_class; +}; + +CamelType camel_imapp_driver_get_type (void); + +CamelIMAPPDriver * camel_imapp_driver_new(CamelIMAPPStream *stream); + +void camel_imapp_driver_set_sasl_factory(CamelIMAPPDriver *id, CamelIMAPPSASLFunc get_sasl, void *sasl_data); +void camel_imapp_driver_set_login_query(CamelIMAPPDriver *id, CamelIMAPPLoginFunc get_login, void *login_data); + +void camel_imapp_driver_login(CamelIMAPPDriver *id); + +void camel_imapp_driver_select(CamelIMAPPDriver *id, struct _CamelIMAPPFolder *folder); +void camel_imapp_driver_update(CamelIMAPPDriver *id, struct _CamelIMAPPFolder *folder); +void camel_imapp_driver_sync(CamelIMAPPDriver *id, gboolean expunge, struct _CamelIMAPPFolder *folder); + +struct _CamelStream * camel_imapp_driver_fetch(CamelIMAPPDriver *id, struct _CamelIMAPPFolder *folder, const char *uid, const char *body); +GPtrArray * camel_imapp_driver_list(CamelIMAPPDriver *id, const char *name, guint32 flags); + +struct _CamelStream *camel_imapp_driver_get(CamelIMAPPDriver *id, struct _CamelIMAPPFolder *folder, const char *uid); +void camel_imapp_driver_append(CamelIMAPPDriver *id, struct _CamelIMAPPFolder *folder, struct _CamelDataWrapper *); + +#endif diff --git a/camel/providers/imapp/camel-imapp-engine.c b/camel/providers/imapp/camel-imapp-engine.c new file mode 100644 index 0000000000..f6cfcbf86c --- /dev/null +++ b/camel/providers/imapp/camel-imapp-engine.c @@ -0,0 +1,1156 @@ + +#include "config.h" + +#include <stdio.h> +#include <string.h> + +#include "camel-imapp-engine.h" +#include "camel-imapp-stream.h" +#include "camel-imapp-utils.h" +#include "camel-imapp-exception.h" + +#include <camel/camel-folder-summary.h> +#include <camel/camel-stream-mem.h> +#include <camel/camel-stream-null.h> +#include <camel/camel-data-wrapper.h> +#include <camel/camel-sasl.h> + +#include <ctype.h> + +#define e(x) +#define c(x) /* command build debug */ + +static void imap_engine_command_addv(CamelIMAPPEngine *imap, CamelIMAPPCommand *ic, const char *fmt, va_list ap); +static void imap_engine_command_complete(CamelIMAPPEngine *imap, CamelIMAPPCommand *ic); + +struct _handler { + CamelIMAPPEngineFunc func; + void *data; + char name[1]; +}; + +static void +class_init(CamelIMAPPEngineClass *ieclass) +{ + ieclass->tagprefix = 'A'; + + camel_object_class_add_event((CamelObjectClass *)ieclass, "status", NULL); +} + +static void +object_init(CamelIMAPPEngine *ie, CamelIMAPPEngineClass *ieclass) +{ + ie->handlers = g_hash_table_new(g_str_hash, g_str_equal); + e_dlist_init(&ie->active); + e_dlist_init(&ie->queue); + + ie->tagprefix = ieclass->tagprefix; + ieclass->tagprefix++; + if (ieclass->tagprefix > 'Z') + ieclass->tagprefix = 'A'; + ie->tagprefix = 'A'; + + ie->state = IMAP_ENGINE_DISCONNECT; +} + +static void +handler_free(void *key, void *mem, void *data) +{ + g_free(mem); +} + +static void +object_finalise(CamelIMAPPEngine *ie, CamelIMAPPEngineClass *ieclass) +{ + /* FIXME: need to free the commands ... */ + while (camel_imapp_engine_iterate(ie, NULL) > 0) + ; + + g_hash_table_foreach(ie->handlers, (GHFunc)handler_free, NULL); + g_hash_table_destroy(ie->handlers); +} + +CamelType +camel_imapp_engine_get_type (void) +{ + static CamelType type = CAMEL_INVALID_TYPE; + + if (type == CAMEL_INVALID_TYPE) { + type = camel_type_register ( + camel_object_get_type (), + "CamelIMAPPEngine", + sizeof (CamelIMAPPEngine), + sizeof (CamelIMAPPEngineClass), + (CamelObjectClassInitFunc) class_init, + NULL, + (CamelObjectInitFunc) object_init, + (CamelObjectFinalizeFunc) object_finalise); + } + + return type; +} + +/* FIXME: check this, just taken from old code, not rfc */ +struct { + char *name; + guint32 flag; +} capa_table[] = { + { "IMAP4", IMAP_CAPABILITY_IMAP4 }, + { "IMAP4REV1", IMAP_CAPABILITY_IMAP4REV1 }, + { "STATUS", IMAP_CAPABILITY_STATUS } , + { "NAMESPACE", IMAP_CAPABILITY_NAMESPACE }, + { "UIDPLUS", IMAP_CAPABILITY_UIDPLUS }, + { "LITERAL+", IMAP_CAPABILITY_LITERALPLUS }, + { "STARTTLS", IMAP_CAPABILITY_STARTTLS }, +}; + + +/* +capability_data ::= "CAPABILITY" SPACE [1#capability SPACE] "IMAP4rev1" + [SPACE 1#capability] + ;; IMAP4rev1 servers which offer RFC 1730 + ;; compatibility MUST list "IMAP4" as the first + ;; capability. +*/ +static int resp_capability(CamelIMAPPEngine *ie, guint32 id, void *data) +{ + int tok, len, i; + unsigned char *token, *p, c; + + /* FIXME: handle auth types */ + + printf("got capability response:\n"); + while (1) { + tok = camel_imapp_stream_token(ie->stream, &token, &len); + switch(tok) { + case IMAP_TOK_TOKEN: + p = token; + while ((c = *p)) + *p++ = toupper(c); + case IMAP_TOK_INT: + printf(" cap: '%s'\n", token); + for (i=0;i<(int)(sizeof(capa_table)/sizeof(capa_table[0]));i++) + if (strcmp(token, capa_table[i].name)) + ie->capa |= capa_table[i].flag; + break; + case '\n': + return 0; + case IMAP_TOK_ERROR: + case IMAP_TOK_PROTOCOL: + camel_imapp_engine_skip(ie); + return -1; + default: + printf("Unknown Response token %02x '%c'\n", tok, isprint(tok)?tok:'.'); + } + } while (tok != '\n'); + + return 0; +} + +/* expunge command, id is expunged seq number */ +/* message_data ::= nz_number SPACE ("EXPUNGE" / + ("FETCH" SPACE msg_att)) */ +static int resp_expunge(CamelIMAPPEngine *ie, guint32 id, void *data) +{ + printf("message expunged: %d\n", id); + + return camel_imapp_engine_skip(ie); +} + +static int resp_flags(CamelIMAPPEngine *ie, guint32 id, void *data) +{ + guint32 flags; + + imap_parse_flags(ie->stream, &flags); + + printf("flags: %08x\n", flags); + + return camel_imapp_engine_skip(ie); +} + +/* exists count */ +static int resp_exists(CamelIMAPPEngine *ie, guint32 id, void *data) +{ + printf("messages exist: %d\n", id); + + if (ie->select_response) + ie->select_response->exists = id; + + return camel_imapp_engine_skip(ie); +} + +static int resp_recent(CamelIMAPPEngine *ie, guint32 id, void *data) +{ + printf("messages recent: %d\n", id); + + if (ie->select_response) + ie->select_response->recent = id; + + return camel_imapp_engine_skip(ie); +} + +static int resp_fetch(CamelIMAPPEngine *ie, guint32 id, void *data) +{ + struct _fetch_info *finfo; + + finfo = imap_parse_fetch(ie->stream); + imap_dump_fetch(finfo); + imap_free_fetch(finfo); + + return camel_imapp_engine_skip(ie); +} + +#if 0 +static int resp_list(CamelIMAPPEngine *ie, guint32 id, void *data) +{ + struct _list_info *linfo; + + linfo = imap_parse_list(ie->stream); + printf("list: '%s' (%c)\n", linfo->name, linfo->separator); + imap_free_list(linfo); + + return camel_imapp_engine_skip(ie); +} +#endif + +CamelIMAPPEngine * +camel_imapp_engine_new(CamelIMAPPStream *stream) +{ + CamelIMAPPEngine * volatile engine; + + engine = CAMEL_IMAPP_ENGINE (camel_object_new (CAMEL_IMAPP_ENGINE_TYPE)); + engine->stream = stream; + camel_object_ref((CamelObject *)stream); + + camel_imapp_engine_add_handler(engine, "CAPABILITY", resp_capability, engine); + + /* mailbox_data */ + camel_imapp_engine_add_handler(engine, "FLAGS", (CamelIMAPPEngineFunc)resp_flags, engine); + camel_imapp_engine_add_handler(engine, "EXISTS", (CamelIMAPPEngineFunc)resp_exists, engine); + camel_imapp_engine_add_handler(engine, "RECENT", (CamelIMAPPEngineFunc)resp_recent, engine); + +#if 0 + camel_imapp_engine_add_handler(engine, "LIST", (CamelIMAPPEngineFunc)resp_list, engine); + camel_imapp_engine_add_handler(engine, "LSUB", (CamelIMAPPEngineFunc)resp_list, engine); +#endif + /* message_data */ + camel_imapp_engine_add_handler(engine, "EXPUNGE", (CamelIMAPPEngineFunc)resp_expunge, engine); + camel_imapp_engine_add_handler(engine, "FETCH", (CamelIMAPPEngineFunc)resp_fetch, engine); + + /* TODO: move this to a driver:connect call? */ + CAMEL_TRY { + unsigned char *token; + unsigned int len; + int tok; + + tok = camel_imapp_stream_token(stream, &token, &len); + if (tok == '*') { + struct _status_info *sinfo = imap_parse_status(stream); + + switch (sinfo->result) { + case IMAP_OK: + engine->state = IMAP_ENGINE_CONNECT; + printf("Server connected ok: %s\n", sinfo->text); + break; + case IMAP_PREAUTH: + printf("pre-authenticated ...\n"); + engine->state = IMAP_ENGINE_AUTH; + break; + default: + imap_free_status(sinfo); + camel_exception_throw(1, "Server refused connection: %s", sinfo->text); + break; + } + imap_free_status(sinfo); + } else { + engine->state = IMAP_ENGINE_CONNECT; + printf("unknwon server greeting, ignored\n"); + camel_imapp_engine_skip(engine); + } + camel_imapp_engine_capabilities(engine); + } CAMEL_CATCH(ex) { + printf("connection failed: %s\n", ex->desc); + camel_object_unref((CamelObject *)engine); + engine = NULL; + } CAMEL_DONE; + + return engine; +} + +void +camel_imapp_engine_add_handler(CamelIMAPPEngine *imap, const char *response, CamelIMAPPEngineFunc func, void *data) +{ + struct _handler *h; + const unsigned char *p; + unsigned char *o, c; + + h = g_malloc0(sizeof(*h) + strlen(response)); + h->func = func; + h->data = data; + + p = response; + o = h->name; + while ((c = *p++)) + *o++ = toupper(c); + *o = 0; + + g_hash_table_insert(imap->handlers, h->name, h); +} + +int +camel_imapp_engine_capabilities(CamelIMAPPEngine *ie) +{ + CamelIMAPPCommand *ic; + + /* reset capabilities */ + ie->capa = 0; + + ic = camel_imapp_engine_command_new(ie, "CAPABILITY", NULL, "CAPABILITY"); + camel_imapp_engine_command_queue(ie, ic); + while (camel_imapp_engine_iterate(ie, ic)>0) + ; + camel_imapp_engine_command_free(ie, ic); + + return 0; +} + +/* skip the rest of the line of tokens */ +int +camel_imapp_engine_skip(CamelIMAPPEngine *imap) +{ + int tok; + unsigned char *token; + unsigned int len; + + do { + tok = camel_imapp_stream_token(imap->stream, &token, &len); + if (tok == IMAP_TOK_LITERAL) { + camel_imapp_stream_set_literal(imap->stream, len); + while ((tok = camel_imapp_stream_getl(imap->stream, &token, &len)) > 0) { + printf("Skip literal data '%.*s'\n", (int)len, token); + } + } + } while (tok != '\n' && tok >= 0); + + if (tok < 0) + return -1; + + return 0; +} + +/* handle any untagged responses */ +int +iterate_untagged(CamelIMAPPEngine *imap) +{ + unsigned int id, len; + unsigned char *token, *p, c; + int tok; + struct _handler *h; + struct _status_info *sinfo; + + e(printf("got untagged response\n")); + id = 0; + tok = camel_imapp_stream_token(imap->stream, &token, &len); + if (tok == IMAP_TOK_INT) { + id = strtoul(token, NULL, 10); + tok = camel_imapp_stream_token(imap->stream, &token, &len); + } + + if (tok == '\n') + camel_exception_throw(1, "truncated server response"); + + e(printf("Have token '%s' id %d\n", token, id)); + p = token; + while ((c = *p)) + *p++ = toupper(c); + + /* first, check for generic unsolicited response */ + h = g_hash_table_lookup(imap->handlers, token); + if (h) { + tok = h->func(imap, id, h->data); + if (tok < 0) + return tok; + return 1; + } + + /* TODO: apart from bye/preauth, these could be callbacks/events? */ + + /* now, check for status responses */ + switch (imap_tokenise(token, len)) { + case IMAP_BYE: + case IMAP_OK: + case IMAP_NO: + case IMAP_BAD: + case IMAP_PREAUTH: + /* TODO: validate which ones of these can happen as unsolicited responses */ + /* TODO: handle bye/preauth differently */ + /* FIXME: free sinfo */ + camel_imapp_stream_ungettoken(imap->stream, tok, token, len); + sinfo = imap_parse_status(imap->stream); + camel_object_trigger_event(imap, "status", sinfo); + imap_free_status(sinfo); +#if 0 + switch(sinfo->condition) { + case IMAP_READ_WRITE: + printf("folder is read-write\n"); + break; + case IMAP_READ_ONLY: + printf("folder is read-only\n"); + break; + case IMAP_UIDVALIDITY: + if (imap->select_response) + imap->select_response->uidvalidity = sinfo->u.uidvalidity; + break; +#if 0 + /* not defined yet ... */ + case IMAP_UIDNEXT: + printf("got uidnext for folder: %d\n", sinfo->u.uidnext); + break; +#endif + case IMAP_UNSEEN: + if (imap->select_response) + imap->select_response->unseen = sinfo->u.unseen; + break; + case IMAP_PERMANENTFLAGS: + if (imap->select_response) + imap->select_response->permanentflags = sinfo->u.permanentflags; + break; + case IMAP_ALERT: + printf("ALERT!: %s\n", sinfo->text); + break; + case IMAP_PARSE: + printf("PARSE: %s\n", sinfo->text); + break; + default: + break; + } +#endif + break; + default: + printf("unknown token: %s\n", token); + camel_imapp_engine_skip(imap); + /* unknown response, just ignore it */ + } + + return 1; +} + +/* handle any continuation requests + either data continuations, or auth continuation */ +int +iterate_continuation(CamelIMAPPEngine *imap) +{ + CamelIMAPPCommand *ic; + CamelIMAPPCommandPart *cp; + + printf("got continuation response\n"); + + ic = imap->literal; + imap->literal = NULL; + if (ic == NULL) { + camel_imapp_engine_skip(imap); + printf("got continuation response with no outstanding continuation requests?\n"); + return 1; + } + + printf("got continuation response for data\n"); + cp = ic->current; + switch(cp->type & CAMEL_IMAPP_COMMAND_MASK) { + case CAMEL_IMAPP_COMMAND_DATAWRAPPER: + printf("writing data wrapper to literal\n"); + camel_data_wrapper_write_to_stream((CamelDataWrapper *)cp->ob, (CamelStream *)imap->stream); + break; + case CAMEL_IMAPP_COMMAND_STREAM: + printf("writing stream to literal\n"); + camel_stream_write_to_stream((CamelStream *)cp->ob, (CamelStream *)imap->stream); + break; + case CAMEL_IMAPP_COMMAND_AUTH: { + CamelException *ex = camel_exception_new(); + char *resp; + unsigned char *token; + int tok, len; + + tok = camel_imapp_stream_token(imap->stream, &token, &len); + resp = camel_sasl_challenge_base64((CamelSasl *)cp->ob, token, ex); + if (camel_exception_is_set(ex)) + camel_exception_throw_ex(ex); + camel_exception_free(ex); + + printf("got auth continuation, feeding token '%s' back to auth mech\n", resp); + + camel_stream_write((CamelStream *)imap->stream, resp, strlen(resp)); + + /* we want to keep getting called until we get a status reponse from the server + ignore what sasl tells us */ + imap->literal = ic; + + break; } + default: + /* should we just ignore? */ + camel_exception_throw(1, "continuation response for non-continuation request"); + } + + camel_imapp_engine_skip(imap); + + cp = cp->next; + if (cp->next) { + ic->current = cp; + printf("next part of command \"A%05u: %s\"\n", ic->tag, cp->data); + camel_stream_printf((CamelStream *)imap->stream, "%s\r\n", cp->data); + if (cp->type & CAMEL_IMAPP_COMMAND_CONTINUATION) { + imap->literal = ic; + } else { + g_assert(cp->next->next == NULL); + } + } else { + printf("%p: queueing continuation\n", ic); + camel_stream_printf((CamelStream *)imap->stream, "\r\n"); + } + + if (imap->literal == NULL) { + ic = (CamelIMAPPCommand *)e_dlist_remhead(&imap->queue); + if (ic) { + printf("found outstanding op, queueing\n"); + camel_imapp_engine_command_queue(imap, ic); + } + } + + return 1; +} + +/* handle a completion line */ +int +iterate_completion(CamelIMAPPEngine *imap, unsigned char *token) +{ + CamelIMAPPCommand *ic; + unsigned int tag; + + if (token[0] != imap->tagprefix) + camel_exception_throw(1, "Server sent unexpected response: %s", token); + + tag = strtoul(token+1, NULL, 10); + ic = camel_imapp_engine_command_find_tag(imap, tag); + if (ic) { + printf("Got completion response for command %05u '%s'\n", ic->tag, ic->name); + printf("%p: removing command from qwueue, we were at '%s'\n", ic, ic->current->data); + printf("%p: removing command\n", ic); + e_dlist_remove((EDListNode *)ic); + if (imap->literal == ic) + imap->literal = NULL; + ic->status = imap_parse_status(imap->stream); + printf("got response code: %s\n", ic->status->text); + + /* TODO: remove this stuff and use a completion handler? */ + /* TODO: handle 'SELECT' command cleanup here */ + /* FIXME: have this use tokeniser, have this handle close/logout/select etc as well */ + /* ok response from login/authenticate, then we're in happy land */ + if ((!strcmp(ic->name, "LOGIN") || !strcmp(ic->name, "AUTHENTICATE")) + && ic->status->result == IMAP_OK) + imap->state = IMAP_ENGINE_AUTH; + + if (ic->complete) + ic->complete(imap, ic, ic->complete_data); + } else { + camel_exception_throw(1, "got response tag unexpectedly: %s", token); + } + + if (imap->literal != NULL) { + printf("Warning: continuation command '%s' finished with outstanding continuation\n", imap->literal->name); + ic = imap->literal; + /* set the command complete with a failure code? */ + e_dlist_remove((EDListNode *)ic); + imap->literal = NULL; + } + + ic = (CamelIMAPPCommand *)e_dlist_remhead(&imap->queue); + if (ic) { + printf("found outstanding op, queueing\n"); + camel_imapp_engine_command_queue(imap, ic); + } + + return 1; +} + + +/* Do work if there's any to do */ +int +camel_imapp_engine_iterate(CamelIMAPPEngine *imap, CamelIMAPPCommand *icwait) +/* throws IO,PARSE exception */ +{ + unsigned int len; + unsigned char *token; + int tok; + + if ((icwait && icwait->status != NULL) || e_dlist_empty(&imap->active)) + return 0; + + /* handle exceptions here? */ + + /* lock here? */ + + tok = camel_imapp_stream_token(imap->stream, &token, &len); + if (tok == '*') + iterate_untagged(imap); + else if (tok == IMAP_TOK_TOKEN) + iterate_completion(imap, token); + else if (tok == '+') + iterate_continuation(imap); + else + camel_exception_throw(1, "unexpected server response: %s", token); + + if (e_dlist_empty(&imap->active)) + return 0; + + return 1; +} + +CamelIMAPPCommand * +camel_imapp_engine_command_new(CamelIMAPPEngine *imap, const char *name, const char *select, const char *fmt, ...) +{ + CamelIMAPPCommand *ic; + va_list ap; + + ic = g_malloc0(sizeof(*ic)); + ic->tag = imap->tag++; + ic->name = name; + ic->mem = (CamelStreamMem *)camel_stream_mem_new(); + ic->select = g_strdup(select); + e_dlist_init(&ic->parts); + + if (fmt && fmt[0]) { + va_start(ap, fmt); + imap_engine_command_addv(imap, ic, fmt, ap); + va_end(ap); + } + + return ic; +} + +void +camel_imapp_engine_command_add(CamelIMAPPEngine *imap, CamelIMAPPCommand *ic, const char *fmt, ...) +{ + va_list ap; + + g_assert(ic->mem); /* gets reset on queue */ + + if (fmt && fmt[0]) { + va_start(ap, fmt); + imap_engine_command_addv(imap, ic, fmt, ap); + va_end(ap); + } +} + +void +camel_imapp_engine_command_complete(CamelIMAPPEngine *imap, struct _CamelIMAPPCommand *ic, CamelIMAPPCommandFunc func, void *data) +{ + ic->complete = func; + ic->complete_data = data; +} + +/* FIXME: make imap command's refcounted */ +void +camel_imapp_engine_command_free (CamelIMAPPEngine *imap, CamelIMAPPCommand *ic) +{ + CamelIMAPPCommandPart *cp, *cn; + + if (ic == NULL) + return; + + /* validity check - we cant' free items still in any queue ... */ + /* maybe we should just have another queue to keep them? */ + { + CamelIMAPPCommand *iw; + + iw = (CamelIMAPPCommand *)imap->active.head; + while (iw->next) { + if (iw == ic) + abort(); + iw = iw->next; + } + iw = (CamelIMAPPCommand *)imap->queue.head; + while (iw->next) { + if (iw == ic) + abort(); + iw = iw->next; + } + } + + if (ic->mem) + camel_object_unref((CamelObject *)ic->mem); + imap_free_status(ic->status); + g_free(ic->select); + + cp = (CamelIMAPPCommandPart *)ic->parts.head; + cn = cp->next; + while (cn) { + g_free(cp->data); + if (cp->ob) + camel_object_unref(cp->ob); + g_free(cp); + cp = cn; + cn = cn->next; + } + + g_free(ic); +} + +/* FIXME: error handling */ +void +camel_imapp_engine_command_queue(CamelIMAPPEngine *imap, CamelIMAPPCommand *ic) +{ + CamelIMAPPCommandPart *cp; + + if (ic->mem) + imap_engine_command_complete(imap, ic); + + /* FIXME: remove select stuff */ + + /* see if we need to pre-queue a select command to select the right folder first */ + if (ic->select && (imap->last_select == NULL || strcmp(ic->select, imap->last_select) != 0)) { + CamelIMAPPCommand *select; + + /* of course ... we can't do anything like store/search if we have to select + first, because it'll mess up all the sequence numbers ... hrm ... bugger */ + + select = camel_imapp_engine_command_new(imap, "SELECT", NULL, "SELECT %s", ic->select); + g_free(imap->last_select); + imap->last_select = g_strdup(ic->select); + camel_imapp_engine_command_queue(imap, select); + /* how does it get freed? handle inside engine? */ + } + + /* first, check if command can be sent yet ... queue if not */ + if (imap->literal != NULL) { + printf("%p: queueing while literal active\n", ic); + e_dlist_addtail(&imap->queue, (EDListNode *)ic); + return; + } + + cp = (CamelIMAPPCommandPart *)ic->parts.head; + g_assert(cp); + ic->current = cp; + + /* how to handle exceptions here? */ + + printf("queueing command \"%c%05u %s\"\n", imap->tagprefix, ic->tag, cp->data); + camel_stream_printf((CamelStream *)imap->stream, "%c%05u %s\r\n", imap->tagprefix, ic->tag, cp->data); + + if (cp->type & CAMEL_IMAPP_COMMAND_CONTINUATION) { + printf("%p: active literal\n", ic); + g_assert(cp->next); + imap->literal = ic; + e_dlist_addtail(&imap->active, (EDListNode *)ic); + } else { + printf("%p: active non-literal\n", ic); + g_assert(cp->next && cp->next->next == NULL); + e_dlist_addtail(&imap->active, (EDListNode *)ic); + } +} + +CamelIMAPPCommand * +camel_imapp_engine_command_find (CamelIMAPPEngine *imap, const char *name) +{ + CamelIMAPPCommand *ic, *in; + + ic = imap->literal; + if (ic && strcmp(ic->name, name) == 0) + return ic; + + /* first, try active */ + ic = (CamelIMAPPCommand *)imap->active.head; + in = ic->next; + while (in) { + if (strcmp(ic->name, name) == 0) + return ic; + ic = in; + in = in->next; + } + + return NULL; +} + +CamelIMAPPCommand * +camel_imapp_engine_command_find_tag(CamelIMAPPEngine *imap, unsigned int tag) +{ + CamelIMAPPCommand *ic, *in; + + ic = imap->literal; + if (ic && ic->tag == tag) + return ic; + + ic = (CamelIMAPPCommand *)imap->active.head; + in = ic->next; + while (in) { + if (ic->tag == tag) + return ic; + ic = in; + in = in->next; + } + + return NULL; +} + +/* ********************************************************************** */ + +CamelIMAPPSelectResponse * +camel_imapp_engine_select(CamelIMAPPEngine *imap, const char *name) +{ + CamelIMAPPSelectResponse * volatile resp; + CamelIMAPPCommand * volatile ic = NULL; + + resp = g_malloc0(sizeof(*resp)); + imap->select_response = resp; + + CAMEL_TRY { + ic = camel_imapp_engine_command_new(imap, "SELECT", NULL, "SELECT %s", name); + camel_imapp_engine_command_queue(imap, ic); + while (camel_imapp_engine_iterate(imap, ic) > 0) + ; + + if (ic->status->result != IMAP_OK) + camel_exception_throw(1, "select failed: %s", ic->status->text); + resp->status = ic->status; + ic->status = NULL; + } CAMEL_CATCH (e) { + camel_imapp_engine_command_free(imap, ic); + camel_imapp_engine_select_free(imap, resp); + imap->select_response = NULL; + camel_exception_throw_ex(e); + } CAMEL_DONE; + + camel_imapp_engine_command_free(imap, ic); + imap->select_response = NULL; + + return resp; +} + +void +camel_imapp_engine_select_free(CamelIMAPPEngine *imap, CamelIMAPPSelectResponse *select) +{ + if (select) { + imap_free_status(select->status); + g_free(select); + } +} + +/* ********************************************************************** */ + +static void +imap_engine_command_add_part(CamelIMAPPEngine *imap, CamelIMAPPCommand *ic, camel_imapp_command_part_t type, CamelObject *ob) +{ + CamelIMAPPCommandPart *cp; + CamelStreamNull *null; + unsigned int ob_size = 0; + + switch(type & CAMEL_IMAPP_COMMAND_MASK) { + case CAMEL_IMAPP_COMMAND_DATAWRAPPER: + case CAMEL_IMAPP_COMMAND_STREAM: + null = (CamelStreamNull *)camel_stream_null_new(); + if ( (type & CAMEL_IMAPP_COMMAND_MASK) == CAMEL_IMAPP_COMMAND_DATAWRAPPER) { + camel_data_wrapper_write_to_stream((CamelDataWrapper *)ob, (CamelStream *)null); + } else { + camel_stream_reset((CamelStream *)ob); + camel_stream_write_to_stream((CamelStream *)ob, (CamelStream *)null); + camel_stream_reset((CamelStream *)ob); + } + type |= CAMEL_IMAPP_COMMAND_CONTINUATION; + camel_object_ref(ob); + ob_size = null->written; + camel_object_unref((CamelObject *)null); + camel_stream_printf((CamelStream *)ic->mem, "{%u}", ob_size); + break; + case CAMEL_IMAPP_COMMAND_AUTH: + /* we presume we'll need to get additional data only if we're not authenticated yet */ + camel_object_ref(ob); + camel_stream_printf((CamelStream *)ic->mem, "%s", ((CamelSasl *)ob)->mech); + if (!camel_sasl_authenticated((CamelSasl *)ob)) + type |= CAMEL_IMAPP_COMMAND_CONTINUATION; + break; + default: + ob_size = 0; + } + + cp = g_malloc0(sizeof(*cp)); + cp->type = type; + cp->ob_size = ob_size; + cp->ob = ob; + cp->data_size = ic->mem->buffer->len; + cp->data = g_malloc(cp->data_size+1); + memcpy(cp->data, ic->mem->buffer->data, cp->data_size); + cp->data[cp->data_size] = 0; + + camel_stream_reset((CamelStream *)ic->mem); + /* FIXME: hackish? */ + g_byte_array_set_size(ic->mem->buffer, 0); + + e_dlist_addtail(&ic->parts, (EDListNode *)cp); +} + +static int len(EDList *list) +{ + int count = 0; + EDListNode *n = list->head; + + while (n->next) { + n = n->next; + count++; + } + return count; +} + +static void +imap_engine_command_complete(CamelIMAPPEngine *imap, CamelIMAPPCommand *ic) +{ + c(printf("completing command buffer is [%d] '%.*s'\n", ic->mem->buffer->len, (int)ic->mem->buffer->len, ic->mem->buffer->data)); + c(printf("command has %d parts\n", len(&ic->parts))); + if (ic->mem->buffer->len > 0) + imap_engine_command_add_part(imap, ic, CAMEL_IMAPP_COMMAND_SIMPLE, NULL); + + c(printf("command has %d parts\n", len(&ic->parts))); + + camel_object_unref((CamelObject *)ic->mem); + ic->mem = NULL; +} + +static void +imap_engine_command_addv(CamelIMAPPEngine *imap, CamelIMAPPCommand *ic, const char *fmt, va_list ap) +{ + const unsigned char *p, *ps, *start; + unsigned char c; + unsigned int width; + char ch; + int llong; + int left; + int fill; + int zero; + char *s; + int d; + long int l; + guint32 f; + CamelStream *S; + CamelDataWrapper *D; + CamelSasl *A; + char buffer[16]; + + c(printf("adding command, fmt = '%s'\n", fmt)); + + p = fmt; + ps = fmt; + while ( ( c = *p++ ) ) { + switch(c) { + case '%': + if (*p == '%') { + camel_stream_write((CamelStream *)ic->mem, ps, p-ps); + p++; + ps = p; + } else { + camel_stream_write((CamelStream *)ic->mem, ps, p-ps-1); + start = p-1; + width = 0; + left = FALSE; + fill = FALSE; + zero = FALSE; + llong = FALSE; + + do { + c = *p++; + if (c == '0') + zero = TRUE; + else if ( c== '-') + left = TRUE; + else + break; + } while (c); + + do { + if (isdigit(c)) + width = width * 10 + (c-'0'); + else + break; + } while ((c = *p++)); + + if (c == 'l') { + llong = TRUE; + c = *p++; + } + + switch(c) { + case 'A': /* auth object - sasl auth, treat as special kind of continuation */ + A = va_arg(ap, CamelSasl *); + imap_engine_command_add_part(imap, ic, CAMEL_IMAPP_COMMAND_AUTH, (CamelObject *)A); + break; + case 'S': /* stream */ + S = va_arg(ap, CamelStream *); + c(printf("got stream '%p'\n", S)); + imap_engine_command_add_part(imap, ic, CAMEL_IMAPP_COMMAND_STREAM, (CamelObject *)S); + break; + case 'D': /* datawrapper */ + D = va_arg(ap, CamelDataWrapper *); + c(printf("got data wrapper '%p'\n", D)); + imap_engine_command_add_part(imap, ic, CAMEL_IMAPP_COMMAND_DATAWRAPPER, (CamelObject *)D); + break; + case 't': /* token */ + s = va_arg(ap, char *); + camel_stream_write((CamelStream *)ic->mem, s, strlen(s)); + break; + case 's': /* simple string */ + s = va_arg(ap, char *); + c(printf("got string '%s'\n", s)); + /* FIXME: escpae chars, convert to literal or literal+, etc */ + camel_stream_printf((CamelStream *)ic->mem, "\"%s\"", s); + break; + case 'f': /* imap folder name */ + s = va_arg(ap, char *); + c(printf("got folder '%s'\n", s)); + /* FIXME: encode folder name */ + /* FIXME: namespace? */ + camel_stream_printf((CamelStream *)ic->mem, "\"%s\"", s?s:""); + break; + case 'F': /* IMAP flags set */ + f = va_arg(ap, guint32); + imap_write_flags((CamelStream *)ic->mem, f); + break; + case 'c': + d = va_arg(ap, int); + ch = d; + camel_stream_write((CamelStream *)ic->mem, &ch, 1); + break; + case 'd': /* int/unsigned */ + case 'u': + if (llong) { + l = va_arg(ap, long int); + c(printf("got long int '%d'\n", (int)l)); + memcpy(buffer, start, p-start); + buffer[p-start] = 0; + camel_stream_printf((CamelStream *)ic->mem, buffer, l); + } else { + d = va_arg(ap, int); + c(printf("got int '%d'\n", d)); + memcpy(buffer, start, p-start); + buffer[p-start] = 0; + camel_stream_printf((CamelStream *)ic->mem, buffer, d); + } + break; + } + + ps = p; + } + break; + case '\\': /* only for \\ really, we dont support \n\r etc at all */ + c = *p; + if (c) { + g_assert(c == '\\'); + camel_stream_write((CamelStream *)ic->mem, ps, p-ps); + p++; + ps = p; + } + } + } + + camel_stream_write((CamelStream *)ic->mem, ps, p-ps-1); +} + + +/* here temporarily while its experimental */ + + +#ifdef ENABLE_THREADS +#include <pthread.h> + +static pthread_key_t handler_key = 0; + +void camel_exception_setup(void) +{ + pthread_key_create(&handler_key, NULL); +} + +#else +/* this is per-thread in threaded mode */ +static struct _CamelExceptionEnv *handler = NULL; + +void camel_exception_setup(void) +{ +} +#endif + +void +camel_exception_try(struct _CamelExceptionEnv *env) +{ +#ifdef ENABLE_THREADS + struct _CamelExceptionEnv *handler; + + handler = pthread_getspecific(handler_key); +#endif + env->parent = handler; + handler = env; + env->ex = NULL; + +#ifdef ENABLE_THREADS + pthread_setspecific(handler_key, handler); +#endif +} + +void +camel_exception_throw_ex(CamelException *ex) +{ + struct _CamelExceptionEnv *env; +#ifdef ENABLE_THREADS + struct _CamelExceptionEnv *handler; + + handler = pthread_getspecific(handler_key); +#endif + printf("throwing exception '%s'\n", ex->desc); + + env = handler; + if (env != NULL) { + env->ex = ex; + handler = env->parent; +#ifdef ENABLE_THREADS + pthread_setspecific(handler_key, handler); +#endif + longjmp(env->env, ex->id); + } else { + g_warning("Uncaught exception: %s\n", ex->desc); + /* we just crash and burn, this is a code problem */ + /* we dont use g_assert_not_reached() since its not a noreturn function */ + abort(); + } +} + +void +camel_exception_throw(int id, char *fmt, ...) +{ + CamelException *ex; + va_list ap; + + ex = camel_exception_new(); + ex->id = id; + va_start(ap, fmt); + ex->desc = g_strdup_vprintf(fmt, ap); + va_end(ap); + + camel_exception_throw_ex(ex); +} + +void +camel_exception_drop(struct _CamelExceptionEnv *env) +{ +#ifdef ENABLE_THREADS + pthread_setspecific(handler_key, env->parent); +#else + handler = env->parent; +#endif +} + +void +camel_exception_done(struct _CamelExceptionEnv *env) +{ +#ifdef ENABLE_THREADS + pthread_setspecific(handler_key, env->parent); +#else + handler = env->parent; +#endif + if (env->ex != NULL) { + camel_exception_free(env->ex); + } +} diff --git a/camel/providers/imapp/camel-imapp-engine.h b/camel/providers/imapp/camel-imapp-engine.h new file mode 100644 index 0000000000..6db9968d48 --- /dev/null +++ b/camel/providers/imapp/camel-imapp-engine.h @@ -0,0 +1,154 @@ + +#ifndef _CAMEL_IMAPP_ENGINE_H +#define _CAMEL_IMAPP_ENGINE_H + +#include <camel/camel-object.h> + +#include "camel-imapp-stream.h" +#include <e-util/e-msgport.h> +#include "camel-imapp-folder.h" + +#define CAMEL_IMAPP_ENGINE_TYPE (camel_imapp_engine_get_type ()) +#define CAMEL_IMAPP_ENGINE(obj) (CAMEL_CHECK_CAST((obj), CAMEL_IMAPP_ENGINE_TYPE, CamelIMAPPEngine)) +#define CAMEL_IMAPP_ENGINE_CLASS(k) (CAMEL_CHECK_CLASS_CAST ((k), CAMEL_IMAPP_ENGINE_TYPE, CamelIMAPPEngineClass)) +#define CAMEL_IS_IMAP_ENGINE(o) (CAMEL_CHECK_TYPE((o), CAMEL_IMAPP_ENGINE_TYPE)) + +typedef struct _CamelIMAPPEngine CamelIMAPPEngine; +typedef struct _CamelIMAPPEngineClass CamelIMAPPEngineClass; + +typedef struct _CamelIMAPPCommandPart CamelIMAPPCommandPart; +typedef struct _CamelIMAPPCommand CamelIMAPPCommand; + +typedef enum { + CAMEL_IMAPP_COMMAND_SIMPLE = 0, + CAMEL_IMAPP_COMMAND_DATAWRAPPER, + CAMEL_IMAPP_COMMAND_STREAM, + CAMEL_IMAPP_COMMAND_AUTH, + CAMEL_IMAPP_COMMAND_MASK = 0xff, + CAMEL_IMAPP_COMMAND_CONTINUATION = 0x8000 /* does this command expect continuation? */ +} camel_imapp_command_part_t; + +struct _CamelIMAPPCommandPart { + struct _CamelIMAPPCommandPart *next; + struct _CamelIMAPPCommandPart *prev; + + struct _CamelIMAPPCommand *parent; + + int data_size; + char *data; + + camel_imapp_command_part_t type; + + int ob_size; + CamelObject *ob; +}; + +typedef int (*CamelIMAPPEngineFunc)(struct _CamelIMAPPEngine *engine, guint32 id, void *data); +typedef void (*CamelIMAPPCommandFunc)(struct _CamelIMAPPEngine *engine, struct _CamelIMAPPCommand *, void *data); + +/* FIXME: make this refcounted */ +struct _CamelIMAPPCommand { + struct _CamelIMAPPCommand *next; + struct _CamelIMAPPCommand *prev; + + const char *name; /* command name/type (e.g. FETCH) */ + + /* FIXME: remove this select stuff */ + char *select; /* if we need to run against a specific folder */ + struct _status_info *status; /* status for command, indicates it is complete if != NULL */ + + unsigned int tag; + + struct _CamelStreamMem *mem; /* for building the part */ + EDList parts; + CamelIMAPPCommandPart *current; + + CamelIMAPPCommandFunc complete; + void *complete_data; +}; + +typedef struct _CamelIMAPPSelectResponse CamelIMAPPSelectResponse; + +struct _CamelIMAPPSelectResponse { + struct _status_info *status; + guint32 exists; + guint32 recent; + guint32 uidvalidity; + guint32 unseen; + guint32 permanentflags; +}; + +enum { + IMAP_CAPABILITY_IMAP4 = (1 << 0), + IMAP_CAPABILITY_IMAP4REV1 = (1 << 1), + IMAP_CAPABILITY_STATUS = (1 << 2), + IMAP_CAPABILITY_NAMESPACE = (1 << 3), + IMAP_CAPABILITY_UIDPLUS = (1 << 4), + IMAP_CAPABILITY_LITERALPLUS = (1 << 5), + IMAP_CAPABILITY_STARTTLS = (1 << 6), +}; + +/* currently selected states */ +typedef enum _camel_imapp_engine_state_t { + IMAP_ENGINE_DISCONNECT, /* only happens during shutdown */ + IMAP_ENGINE_CONNECT, /* connected, not authenticated */ + IMAP_ENGINE_AUTH, /* connected, and authenticated */ + IMAP_ENGINE_SELECT, /* and selected, select holds selected folder */ +} camel_imapp_engine_state_t; + +struct _CamelIMAPPEngine { + CamelObject parent_object; + + CamelIMAPPStream *stream; + + camel_imapp_engine_state_t state; + + guint32 capa; /* capabilities for this server, refresh with :capabilities() */ + + GHashTable *handlers; + + unsigned char tagprefix; /* out tag prefix 'A' 'B' ... 'Z' */ + unsigned int tag; /* next command tag */ + + char *select; /* *currently* selected folder */ + char *last_select; /* last selected or to-be selected folder (e.g. outstanding queued select) */ + CamelIMAPPCommand *literal;/* current literal op */ + EDList active; /* active queue */ + EDList queue; /* outstanding queue */ + + /* keep track of running a select */ + struct _CamelIMAPPSelectResponse *select_response; +}; + +struct _CamelIMAPPEngineClass { + CamelObjectClass parent_class; + + unsigned char tagprefix; + + /* Events: + status(struct _status_info *); + */ +}; + +CamelType camel_imapp_engine_get_type (void); + +CamelIMAPPEngine *camel_imapp_engine_new(CamelIMAPPStream *stream); + +void camel_imapp_engine_add_handler(CamelIMAPPEngine *imap, const char *response, CamelIMAPPEngineFunc func, void *data); +int camel_imapp_engine_iterate(CamelIMAPPEngine *imap, CamelIMAPPCommand *wait); /* throws PARSE,IO exception */ +int camel_imapp_engine_skip(CamelIMAPPEngine *imap); +int camel_imapp_engine_capabilities(CamelIMAPPEngine *imap); + +CamelIMAPPCommand *camel_imapp_engine_command_new (CamelIMAPPEngine *imap, const char *name, const char *select, const char *fmt, ...); +void camel_imapp_engine_command_complete(CamelIMAPPEngine *imap, struct _CamelIMAPPCommand *, CamelIMAPPCommandFunc func, void *data); +void camel_imapp_engine_command_add (CamelIMAPPEngine *imap, CamelIMAPPCommand *ic, const char *fmt, ...); +void camel_imapp_engine_command_free (CamelIMAPPEngine *imap, CamelIMAPPCommand *ic); +void camel_imapp_engine_command_queue(CamelIMAPPEngine *imap, CamelIMAPPCommand *ic); /* throws IO exception */ +CamelIMAPPCommand *camel_imapp_engine_command_find (CamelIMAPPEngine *imap, const char *name); +CamelIMAPPCommand *camel_imapp_engine_command_find_tag(CamelIMAPPEngine *imap, unsigned int tag); + +/* util functions */ +CamelIMAPPSelectResponse *camel_imapp_engine_select(CamelIMAPPEngine *imap, const char *name); +void camel_imapp_engine_select_free(CamelIMAPPEngine *imap, CamelIMAPPSelectResponse *select); + +#endif diff --git a/camel/providers/imapp/camel-imapp-exception.h b/camel/providers/imapp/camel-imapp-exception.h new file mode 100644 index 0000000000..5e18b6c815 --- /dev/null +++ b/camel/providers/imapp/camel-imapp-exception.h @@ -0,0 +1,35 @@ + +/* This implements 'real' exceptions that work a bit like c++/java exceptions */ + +/* Still experimental code */ + +#ifndef __CAMEL_IMAPP_EXCEPTION_H +#define __CAMEL_IMAPP_EXCEPTION_H + +#include <setjmp.h> +#include "camel/camel-exception.h" + +struct _CamelExceptionEnv { + struct _CamelExceptionEnv *parent; + CamelException *ex; + jmp_buf env; +}; + +#define CAMEL_TRY { struct _CamelExceptionEnv __env; camel_exception_try(&__env); if (setjmp(__env.env) == 0) +#define CAMEL_IGNORE camel_exception_done(&__env); } +#define CAMEL_CATCH(x) { CamelException *x; x=__env.ex; if (x != NULL) +#define CAMEL_DONE } camel_exception_done(&__env); } +#define CAMEL_DROP() camel_exception_drop(&__env) + +void camel_exception_setup(void); + +/* internal functions, use macro's above */ +void camel_exception_try(struct _CamelExceptionEnv *env); +void camel_exception_done(struct _CamelExceptionEnv *env); +void camel_exception_drop(struct _CamelExceptionEnv *env); + +/* user functions */ +void camel_exception_throw_ex(CamelException *ex) __attribute__ ((noreturn)); +void camel_exception_throw(int id, char *fmt, ...) __attribute__ ((noreturn)); + +#endif diff --git a/camel/providers/imapp/camel-imapp-fetch-stream.c b/camel/providers/imapp/camel-imapp-fetch-stream.c new file mode 100644 index 0000000000..bf18aac57b --- /dev/null +++ b/camel/providers/imapp/camel-imapp-fetch-stream.c @@ -0,0 +1,183 @@ +/* -*- 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-imapp-stream.h" +#include "camel-imapp-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_IMAPP_FETCH_STREAM_CLASS(CAMEL_OBJECT_GET_CLASS(so)) + +static ssize_t +stream_read(CamelStream *stream, char *buffer, size_t n) +{ + CamelIMAPPFetchStream *is = (CamelIMAPPFetchStream *)stream; + ssize_t max; + + /* make sure we have all the data read in */ + while (camel_imapp_engine_iterate(is->engine, is->command)>0) + ; + + 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; + } + + is->literal -= max; + + return max; +} + +static ssize_t +stream_write(CamelStream *stream, const char *buffer, size_t n) +{ + CamelIMAPPFetchStream *is = (CamelIMAPPFetchStream *)stream; + + 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) +{ + CamelIMAPPFetchStream *is = (CamelIMAPPFetchStream *)stream; + + return is->literal == 0; +} + +static int +stream_reset(CamelStream *stream) +{ + /* nop? reset literal mode? */ + return 0; +} + +static void +camel_imapp_fetch_stream_class_init (CamelStreamClass *camel_imapp_fetch_stream_class) +{ + CamelStreamClass *camel_stream_class = (CamelStreamClass *)camel_imapp_fetch_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_imapp_fetch_stream_init(CamelIMAPPFetchStream *is, CamelIMAPPFetchStreamClass *isclass) +{ + ; +} + +static void +camel_imapp_fetch_stream_finalise(CamelIMAPPFetchStream *is) +{ + if (is->engine) + camel_object_unref(is->engine); +} + +CamelType +camel_imapp_fetch_stream_get_type (void) +{ + static CamelType camel_imapp_fetch_stream_type = CAMEL_INVALID_TYPE; + + if (camel_imapp_fetch_stream_type == CAMEL_INVALID_TYPE) { + setup_table(); + camel_imapp_fetch_stream_type = camel_type_register( camel_stream_get_type(), + "CamelIMAPPFetchStream", + sizeof( CamelIMAPPFetchStream ), + sizeof( CamelIMAPPFetchStreamClass ), + (CamelObjectClassInitFunc) camel_imapp_fetch_stream_class_init, + NULL, + (CamelObjectInitFunc) camel_imapp_fetch_stream_init, + (CamelObjectFinalizeFunc) camel_imapp_fetch_stream_finalise ); + } + + return camel_imapp_fetch_stream_type; +} + +/** + * camel_imapp_fetch_stream_new: + * + * Return value: the stream + **/ +CamelStream * +camel_imapp_fetch_stream_new(CamelIMAPPEngine *ie, const char *uid, const char *body) +{ + CamelIMAPPFetchStream *is; + + is = (CamelIMAPPFetchStream *)camel_object_new(camel_imapp_fetch_stream_get_type ()); + is->engine = ie; + camel_object_ref(ie); + + is->command = camel_imapp_engine_command_new(ie, "FETCH", NULL, "FETCH %t (BODY[%t]<0.4096>", uid, body); + camel_imapp_engine_command_queue(ie, command); + + return (CamelStream *)is; +} + diff --git a/camel/providers/imapp/camel-imapp-fetch-stream.h b/camel/providers/imapp/camel-imapp-fetch-stream.h new file mode 100644 index 0000000000..c9281ff64f --- /dev/null +++ b/camel/providers/imapp/camel-imapp-fetch-stream.h @@ -0,0 +1,47 @@ +/* + * 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_IMAPP_FETCH_STREAM_H +#define _CAMEL_IMAPP_FETCH_STREAM_H + +#include <camel/camel-stream.h> + +#define CAMEL_IMAPP_FETCH_STREAM(obj) CAMEL_CHECK_CAST (obj, camel_imapp_fetch_stream_get_type (), CamelIMAPPFetchStream) +#define CAMEL_IMAPP_FETCH_STREAM_CLASS(klass) CAMEL_CHECK_CLASS_CAST (klass, camel_imapp_fetch_stream_get_type (), CamelIMAPPFetchStreamClass) +#define CAMEL_IS_IMAP_FETCH_STREAM(obj) CAMEL_CHECK_TYPE (obj, camel_imapp_fetch_stream_get_type ()) + +typedef struct _CamelIMAPPFetchStreamClass CamelIMAPPFetchStreamClass; +typedef struct _CamelIMAPPFetchStream CamelIMAPPFetchStream; + +struct _CamelIMAPPFetchStream { + CamelStream parent; + + struct _CamelIMAPPEngine *engine; +}; + +struct _CamelIMAPPFetchStreamClass { + CamelStreamClass parent_class; +}; + +CamelType camel_imapp_fetch_stream_get_type (void); + +CamelStream *camel_imapp_fetch_stream_new (struct _CamelIMAPPEngine *src, const char *uid, const char *spec); + +#endif /* ! _CAMEL_IMAPP_FETCH_STREAM_H */ diff --git a/camel/providers/imapp/camel-imapp-folder.c b/camel/providers/imapp/camel-imapp-folder.c new file mode 100644 index 0000000000..19f3795056 --- /dev/null +++ b/camel/providers/imapp/camel-imapp-folder.c @@ -0,0 +1,268 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* camel-imap-folder.c : class for a imap folder */ + +/* + * Authors: Michael Zucchi <notzed@ximian.com> + * + * Copyright (C) 2002 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 <errno.h> + +#include "camel/camel-exception.h" +#include "camel/camel-stream-mem.h" +#include "camel/camel-stream-filter.h" +#include "camel/camel-mime-message.h" +#include "camel/camel-operation.h" +#include "camel/camel-data-cache.h" +#include "camel/camel-session.h" + +#include "camel-imapp-store.h" +#include "camel-imapp-folder.h" +#include "camel-imapp-summary.h" +#include "camel-imapp-exception.h" + +#include <e-util/md5-utils.h> + +#include <stdlib.h> +#include <string.h> + +#define d(x) + +#define CF_CLASS(o) (CAMEL_FOLDER_CLASS (CAMEL_OBJECT_GET_CLASS(o))) +static CamelFolderClass *parent_class; + +static void imap_finalize (CamelObject *object); +static void imap_refresh_info (CamelFolder *folder, CamelException *ex); +static void imap_sync (CamelFolder *folder, gboolean expunge, CamelException *ex); +static CamelMimeMessage *imap_get_message (CamelFolder *folder, const char *uid, CamelException *ex); + +static void +imap_folder_class_init (CamelIMAPPFolderClass *camel_imapp_folder_class) +{ + CamelFolderClass *camel_folder_class = CAMEL_FOLDER_CLASS(camel_imapp_folder_class); + + parent_class = CAMEL_FOLDER_CLASS(camel_folder_get_type()); + + /* virtual method overload */ + camel_folder_class->refresh_info = imap_refresh_info; + camel_folder_class->sync = imap_sync; + + camel_folder_class->get_message = imap_get_message; + /*camel_folder_class->set_message_flags = imap_set_message_flags;*/ +} + +static void +imap_folder_init(CamelObject *o, CamelObjectClass *klass) +{ + CamelFolder *folder = (CamelFolder *)o; + CamelIMAPPFolder *ifolder = (CamelIMAPPFolder *)o; + + folder->folder_flags |= (CAMEL_FOLDER_HAS_SUMMARY_CAPABILITY | + CAMEL_FOLDER_HAS_SEARCH_CAPABILITY); + + folder->permanent_flags = CAMEL_MESSAGE_ANSWERED | + CAMEL_MESSAGE_DELETED | CAMEL_MESSAGE_DRAFT | + CAMEL_MESSAGE_FLAGGED | CAMEL_MESSAGE_SEEN | CAMEL_MESSAGE_USER; + + /* FIXME: this is just a skeleton */ + + ifolder->changes = camel_folder_change_info_new(); +} + +CamelType +camel_imapp_folder_get_type (void) +{ + static CamelType camel_imapp_folder_type = CAMEL_INVALID_TYPE; + + if (!camel_imapp_folder_type) { + camel_imapp_folder_type = camel_type_register (CAMEL_FOLDER_TYPE, "CamelIMAPPFolder", + sizeof (CamelIMAPPFolder), + sizeof (CamelIMAPPFolderClass), + (CamelObjectClassInitFunc) imap_folder_class_init, + NULL, + imap_folder_init, + (CamelObjectFinalizeFunc) imap_finalize); + } + + return camel_imapp_folder_type; +} + +void +imap_finalize (CamelObject *object) +{ + CamelIMAPPFolder *folder = (CamelIMAPPFolder *)object; + + camel_folder_change_info_free(folder->changes); +} + +CamelFolder * +camel_imapp_folder_new(CamelStore *store, const char *path) +{ + CamelFolder *folder; + char *root; + + d(printf("opening imap folder '%s'\n", path)); + + folder = CAMEL_FOLDER (camel_object_new (CAMEL_IMAPP_FOLDER_TYPE)); + camel_folder_construct(folder, store, path, path); + + ((CamelIMAPPFolder *)folder)->raw_name = g_strdup(path); + + folder->summary = camel_imapp_summary_new(); + + root = camel_session_get_storage_path(((CamelService *)store)->session, (CamelService *)store, NULL); + if (root) { + char *base = g_build_filename(root, path, NULL); + char *file = g_build_filename(base, ".ev-summary", NULL); + + camel_mkdir(base, 0777); + g_free(base); + + camel_folder_summary_set_filename(folder->summary, file); + printf("loading summary from '%s' (root=%s)\n", file, root); + g_free(file); + camel_folder_summary_load(folder->summary); + g_free(root); + } + + return folder; +} + +void +camel_imapp_folder_open(CamelIMAPPFolder *folder, CamelException *ex) +{ + /* */ +} + +void +camel_imapp_folder_delete(CamelIMAPPFolder *folder, CamelException *ex) +{ +} + +void +camel_imapp_folder_rename(CamelIMAPPFolder *folder, const char *new, CamelException *ex) +{ +} + +void +camel_imapp_folder_close(CamelIMAPPFolder *folder, CamelException *ex) +{ +} + +static void +imap_refresh_info (CamelFolder *folder, CamelException *ex) +{ + printf("imapp refresh info?\n"); +} + +static void +imap_sync (CamelFolder *folder, gboolean expunge, CamelException *ex) +{ + camel_imapp_driver_sync(((CamelIMAPPStore *)(folder->parent_store))->driver, expunge, folder); +} + +static CamelMimeMessage * +imap_get_message (CamelFolder *folder, const char *uid, CamelException *ex) +{ + CamelMimeMessage * volatile msg = NULL; + CamelStream * volatile stream = NULL; + + printf("get message '%s'\n", uid); + + CAMEL_TRY { + /* simple implementation, just get whole message in 1 go */ + stream = camel_imapp_driver_fetch(((CamelIMAPPStore *)(folder->parent_store))->driver, (CamelIMAPPFolder *)folder, uid, ""); + camel_stream_reset(stream); + msg = camel_mime_message_new(); + if (camel_data_wrapper_construct_from_stream((CamelDataWrapper *)msg, stream) != -1) { + /* do we care? */ + } + } CAMEL_CATCH(e) { + if (msg) + camel_object_unref(msg); + msg = NULL; + camel_exception_xfer(ex, e); + } CAMEL_DONE; + + if (stream) + camel_object_unref(stream); + + return msg; +} + + +/* Algorithm for selecting a folder: + + - If uidvalidity == old uidvalidity + and exsists == old exists + and recent == old recent + and unseen == old unseen + Assume our summary is correct + for each summary item + mark the summary item as 'old/not updated' + rof + fetch flags from 1:* + for each fetch response + info = summary[index] + if info.uid != uid + info = summary_by_uid[uid] + fi + if info == NULL + create new info @ index + fi + if got.flags + update flags + fi + if got.header + update based on header + mark as retrieved + else if got.body + update based on imap body + mark as retrieved + fi + + Async fetch response: + info = summary[index] + if info == null + if uid == null + force resync/select? + info = empty @ index + else if uid && info.uid != uid + force a resync? + return + fi + + if got.flags { + info.flags = flags + } + if got.header { + info.init(header) + info.empty = false + } + +info.state - 2 bit field in flags + 0 = empty, nothing set + 1 = uid & flags set + 2 = update required + 3 = up to date +*/ + diff --git a/camel/providers/imapp/camel-imapp-folder.h b/camel/providers/imapp/camel-imapp-folder.h new file mode 100644 index 0000000000..329c66b7fc --- /dev/null +++ b/camel/providers/imapp/camel-imapp-folder.h @@ -0,0 +1,66 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* camel-imap-folder.h : Class for a IMAP folder */ + +/* + * Authors: Michael Zucchi <notzed@ximian.com> + * + * Copyright (C) 2002 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 + */ + +#ifndef CAMEL_IMAPP_FOLDER_H +#define CAMEL_IMAPP_FOLDER_H 1 + +#ifdef __cplusplus +extern "C" { +#pragma } +#endif /* __cplusplus }*/ + +#include <camel/camel-folder.h> + +#define CAMEL_IMAPP_FOLDER_TYPE (camel_imapp_folder_get_type ()) +#define CAMEL_IMAPP_FOLDER(obj) (CAMEL_CHECK_CAST((obj), CAMEL_IMAPP_FOLDER_TYPE, CamelIMAPPFolder)) +#define CAMEL_IMAPP_FOLDER_CLASS(k) (CAMEL_CHECK_CLASS_CAST ((k), CAMEL_IMAPP_FOLDER_TYPE, CamelIMAPPFolderClass)) +#define CAMEL_IS_IMAP_FOLDER(o) (CAMEL_CHECK_TYPE((o), CAMEL_IMAPP_FOLDER_TYPE)) + +typedef struct _CamelIMAPPFolder { + CamelFolder parent_object; + + char *raw_name; + CamelFolderChangeInfo *changes; + + guint32 exists; + guint32 recent; + guint32 uidvalidity; + guint32 unseen; + guint32 permanentflags; +} CamelIMAPPFolder; + +typedef struct _CamelIMAPPFolderClass { + CamelFolderClass parent_class; +} CamelIMAPPFolderClass; + +/* Standard Camel function */ +CamelType camel_imapp_folder_get_type (void); + +/* public methods */ +CamelFolder *camel_imapp_folder_new(CamelStore *parent, const char *path); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* CAMEL_IMAPP_FOLDER_H */ diff --git a/camel/providers/imapp/camel-imapp-provider.c b/camel/providers/imapp/camel-imapp-provider.c new file mode 100644 index 0000000000..4abc633498 --- /dev/null +++ b/camel/providers/imapp/camel-imapp-provider.c @@ -0,0 +1,91 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* camel-pop3-provider.c: pop3 provider registration code */ + +/* + * Authors : + * Dan Winship <danw@ximian.com> + * Michael Zucchi <notzed@ximian.com> + * + * Copyright (C) 2002 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 "camel/camel-provider.h" +#include "camel/camel-session.h" +#include "camel/camel-url.h" +#include "camel/camel-sasl.h" + +#include "camel-imapp-store.h" + +CamelProviderConfEntry imapp_conf_entries[] = { + { CAMEL_PROVIDER_CONF_SECTION_START, NULL, NULL, + N_("Message storage") }, + { CAMEL_PROVIDER_CONF_SECTION_END }, + { CAMEL_PROVIDER_CONF_END } +}; + +static CamelProvider imapp_provider = { + "imapp", + + N_("IMAP+"), + + N_("Experimental IMAP 4(.1) client\n" + "This is untested and unsupported code, you want to use plain imap instead.\n\n" + " !!! DO NOT USE THIS FOR PRODUCTION EMAIL !!!\n"), + "mail", + + CAMEL_PROVIDER_IS_REMOTE | CAMEL_PROVIDER_IS_SOURCE | + CAMEL_PROVIDER_IS_STORAGE | CAMEL_PROVIDER_SUPPORTS_SSL, + + CAMEL_URL_NEED_USER | CAMEL_URL_NEED_HOST | CAMEL_URL_ALLOW_AUTH, + + imapp_conf_entries, + + /* ... */ +}; + +CamelServiceAuthType camel_imapp_password_authtype = { + N_("Password"), + + N_("This option will connect to the IMAP server using a " + "plaintext password."), + + "", + TRUE +}; + +void +camel_imapp_module_init(CamelSession *session) +{ + imapp_provider.object_types[CAMEL_PROVIDER_STORE] = camel_imapp_store_get_type(); + imapp_provider.url_hash = camel_url_hash; + imapp_provider.url_equal = camel_url_equal; + + imapp_provider.authtypes = g_list_prepend(imapp_provider.authtypes, camel_sasl_authtype_list(FALSE)); + imapp_provider.authtypes = g_list_prepend(imapp_provider.authtypes, &camel_imapp_password_authtype); + + camel_session_register_provider(session, &imapp_provider); +} + +void +camel_provider_module_init(CamelSession *session) +{ + camel_imapp_module_init(session); +} diff --git a/camel/providers/imapp/camel-imapp-store-summary.c b/camel/providers/imapp/camel-imapp-store-summary.c new file mode 100644 index 0000000000..a3ad57fb11 --- /dev/null +++ b/camel/providers/imapp/camel-imapp-store-summary.c @@ -0,0 +1,616 @@ +/* + * Copyright (C) 2002 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. + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <unistd.h> +#include <ctype.h> +#include <string.h> +#include <errno.h> +#include <stdlib.h> + +#include "camel-imapp-store-summary.h" + +#include "camel/camel-file-utils.h" + +#include "camel-string-utils.h" +#include "e-util/md5-utils.h" +#include "e-util/e-memory.h" + +#include "camel-private.h" +#include "camel-utf8.h" + +#define d(x) +#define io(x) /* io debug */ + +#define CAMEL_IMAPP_STORE_SUMMARY_VERSION_0 (0) + +#define CAMEL_IMAPP_STORE_SUMMARY_VERSION (0) + +#define _PRIVATE(o) (((CamelIMAPPStoreSummary *)(o))->priv) + +static int summary_header_load(CamelStoreSummary *, FILE *); +static int summary_header_save(CamelStoreSummary *, FILE *); + +/*static CamelStoreInfo * store_info_new(CamelStoreSummary *, const char *);*/ +static CamelStoreInfo * store_info_load(CamelStoreSummary *, FILE *); +static int store_info_save(CamelStoreSummary *, FILE *, CamelStoreInfo *); +static void store_info_free(CamelStoreSummary *, CamelStoreInfo *); + +static const char *store_info_string(CamelStoreSummary *, const CamelStoreInfo *, int); +static void store_info_set_string(CamelStoreSummary *, CamelStoreInfo *, int, const char *); + +static void camel_imapp_store_summary_class_init (CamelIMAPPStoreSummaryClass *klass); +static void camel_imapp_store_summary_init (CamelIMAPPStoreSummary *obj); +static void camel_imapp_store_summary_finalise (CamelObject *obj); + +static CamelStoreSummaryClass *camel_imapp_store_summary_parent; + +static void +camel_imapp_store_summary_class_init (CamelIMAPPStoreSummaryClass *klass) +{ + CamelStoreSummaryClass *ssklass = (CamelStoreSummaryClass *)klass; + + ssklass->summary_header_load = summary_header_load; + ssklass->summary_header_save = summary_header_save; + + /*ssklass->store_info_new = store_info_new;*/ + ssklass->store_info_load = store_info_load; + ssklass->store_info_save = store_info_save; + ssklass->store_info_free = store_info_free; + + ssklass->store_info_string = store_info_string; + ssklass->store_info_set_string = store_info_set_string; +} + +static void +camel_imapp_store_summary_init (CamelIMAPPStoreSummary *s) +{ + /*struct _CamelIMAPPStoreSummaryPrivate *p; + + p = _PRIVATE(s) = g_malloc0(sizeof(*p));*/ + + ((CamelStoreSummary *)s)->store_info_size = sizeof(CamelIMAPPStoreInfo); + s->version = CAMEL_IMAPP_STORE_SUMMARY_VERSION; +} + +static void +camel_imapp_store_summary_finalise (CamelObject *obj) +{ + /*struct _CamelIMAPPStoreSummaryPrivate *p;*/ + /*CamelIMAPPStoreSummary *s = (CamelIMAPPStoreSummary *)obj;*/ + + /*p = _PRIVATE(obj); + g_free(p);*/ +} + +CamelType +camel_imapp_store_summary_get_type (void) +{ + static CamelType type = CAMEL_INVALID_TYPE; + + if (type == CAMEL_INVALID_TYPE) { + camel_imapp_store_summary_parent = (CamelStoreSummaryClass *)camel_store_summary_get_type(); + type = camel_type_register((CamelType)camel_imapp_store_summary_parent, "CamelIMAPPStoreSummary", + sizeof (CamelIMAPPStoreSummary), + sizeof (CamelIMAPPStoreSummaryClass), + (CamelObjectClassInitFunc) camel_imapp_store_summary_class_init, + NULL, + (CamelObjectInitFunc) camel_imapp_store_summary_init, + (CamelObjectFinalizeFunc) camel_imapp_store_summary_finalise); + } + + return type; +} + +/** + * camel_imapp_store_summary_new: + * + * Create a new CamelIMAPPStoreSummary object. + * + * Return value: A new CamelIMAPPStoreSummary widget. + **/ +CamelIMAPPStoreSummary * +camel_imapp_store_summary_new (void) +{ + CamelIMAPPStoreSummary *new = CAMEL_IMAPP_STORE_SUMMARY ( camel_object_new (camel_imapp_store_summary_get_type ())); + + return new; +} + +/** + * camel_imapp_store_summary_full_name: + * @s: + * @path: + * + * Retrieve a summary item by full name. + * + * A referenced to the summary item is returned, which may be + * ref'd or free'd as appropriate. + * + * Return value: The summary item, or NULL if the @full_name name + * is not available. + * It must be freed using camel_store_summary_info_free(). + **/ +CamelIMAPPStoreInfo * +camel_imapp_store_summary_full_name(CamelIMAPPStoreSummary *s, const char *full_name) +{ + int count, i; + CamelIMAPPStoreInfo *info; + + count = camel_store_summary_count((CamelStoreSummary *)s); + for (i=0;i<count;i++) { + info = (CamelIMAPPStoreInfo *)camel_store_summary_index((CamelStoreSummary *)s, i); + if (info) { + if (strcmp(info->full_name, full_name) == 0) + return info; + camel_store_summary_info_free((CamelStoreSummary *)s, (CamelStoreInfo *)info); + } + } + + return NULL; +} + +char * +camel_imapp_store_summary_full_to_path(CamelIMAPPStoreSummary *s, const char *full_name, char dir_sep) +{ + char *path, *p; + int c; + const char *f; + + if (dir_sep != '/') { + p = path = alloca(strlen(full_name)*3+1); + f = full_name; + while ( (c = *f++ & 0xff) ) { + if (c == dir_sep) + *p++ = '/'; + else if (c == '/' || c == '%') + p += sprintf(p, "%%%02X", c); + else + *p++ = c; + } + *p = 0; + } else + path = (char *)full_name; + + return camel_utf7_utf8(path); +} + +static guint32 hexnib(guint32 c) +{ + if (c >= '0' && c <= '9') + return c-'0'; + else if (c>='A' && c <= 'Z') + return c-'A'+10; + else + return 0; +} + +char * +camel_imapp_store_summary_path_to_full(CamelIMAPPStoreSummary *s, const char *path, char dir_sep) +{ + unsigned char *full, *f; + guint32 c, v = 0; + const char *p; + int state=0; + char *subpath, *last = NULL; + CamelStoreInfo *si; + CamelIMAPPStoreNamespace *ns; + + /* check to see if we have a subpath of path already defined */ + subpath = alloca(strlen(path)+1); + strcpy(subpath, path); + do { + si = camel_store_summary_path((CamelStoreSummary *)s, subpath); + if (si == NULL) { + last = strrchr(subpath, '/'); + if (last) + *last = 0; + } + } while (si == NULL && last); + + /* path is already present, use the raw version we have */ + if (si && strlen(subpath) == strlen(path)) { + f = g_strdup(camel_imapp_store_info_full_name(s, si)); + camel_store_summary_info_free((CamelStoreSummary *)s, si); + return f; + } + + ns = camel_imapp_store_summary_namespace_find_path(s, path); + + f = full = alloca(strlen(path)*2+1); + if (si) + p = path + strlen(subpath); + else if (ns) + p = path + strlen(ns->path); + else + p = path; + + while ( (c = camel_utf8_getc((const unsigned char **)&p)) ) { + switch(state) { + case 0: + if (c == '%') + state = 1; + else { + if (c == '/') + c = dir_sep; + camel_utf8_putc(&f, c); + } + break; + case 1: + state = 2; + v = hexnib(c)<<4; + break; + case 2: + state = 0; + v |= hexnib(c); + camel_utf8_putc(&f, v); + break; + } + } + camel_utf8_putc(&f, c); + + /* merge old path part if required */ + f = camel_utf8_utf7(full); + if (si) { + full = g_strdup_printf("%s%s", camel_imapp_store_info_full_name(s, si), f); + g_free(f); + camel_store_summary_info_free((CamelStoreSummary *)s, si); + f = full; + } else if (ns) { + full = g_strdup_printf("%s%s", ns->full_name, f); + g_free(f); + f = full; + } + + return f; +} + +CamelIMAPPStoreInfo * +camel_imapp_store_summary_add_from_full(CamelIMAPPStoreSummary *s, const char *full, char dir_sep) +{ + CamelIMAPPStoreInfo *info; + char *pathu8, *prefix; + int len; + char *full_name; + CamelIMAPPStoreNamespace *ns; + + d(printf("adding full name '%s' '%c'\n", full, dir_sep)); + + len = strlen(full); + full_name = alloca(len+1); + strcpy(full_name, full); + if (full_name[len-1] == dir_sep) + full_name[len-1] = 0; + + info = camel_imapp_store_summary_full_name(s, full_name); + if (info) { + camel_store_summary_info_free((CamelStoreSummary *)s, (CamelStoreInfo *)info); + d(printf(" already there\n")); + return info; + } + + ns = camel_imapp_store_summary_namespace_find_full(s, full_name); + if (ns) { + d(printf("(found namespace for '%s' ns '%s') ", full_name, ns->path)); + len = strlen(ns->full_name); + if (len >= strlen(full_name)) { + pathu8 = g_strdup(ns->path); + } else { + if (full_name[len] == ns->sep) + len++; + + prefix = camel_imapp_store_summary_full_to_path(s, full_name+len, ns->sep); + if (*ns->path) { + pathu8 = g_strdup_printf ("%s/%s", ns->path, prefix); + g_free (prefix); + } else { + pathu8 = prefix; + } + } + d(printf(" (pathu8 = '%s')", pathu8)); + } else { + d(printf("(Cannot find namespace for '%s')\n", full_name)); + pathu8 = camel_imapp_store_summary_full_to_path(s, full_name, dir_sep); + } + + info = (CamelIMAPPStoreInfo *)camel_store_summary_add_from_path((CamelStoreSummary *)s, pathu8); + if (info) { + d(printf(" '%s' -> '%s'\n", pathu8, full_name)); + camel_store_info_set_string((CamelStoreSummary *)s, (CamelStoreInfo *)info, CAMEL_IMAPP_STORE_INFO_FULL_NAME, full_name); + } else + d(printf(" failed\n")); + + return info; +} + +/* should this be const? */ +/* TODO: deprecate/merge this function with path_to_full */ +char * +camel_imapp_store_summary_full_from_path(CamelIMAPPStoreSummary *s, const char *path) +{ + CamelIMAPPStoreNamespace *ns; + char *name = NULL; + + ns = camel_imapp_store_summary_namespace_find_path(s, path); + if (ns) + name = camel_imapp_store_summary_path_to_full(s, path, ns->sep); + + d(printf("looking up path %s -> %s\n", path, name?name:"not found")); + + return name; +} + +/* TODO: this api needs some more work */ +CamelIMAPPStoreNamespace *camel_imapp_store_summary_namespace_new(CamelIMAPPStoreSummary *s, const char *full_name, char dir_sep) +{ + CamelIMAPPStoreNamespace *ns; + char *p; + int len; + + ns = g_malloc0(sizeof(*ns)); + ns->full_name = g_strdup(full_name); + len = strlen(ns->full_name)-1; + if (len >= 0 && ns->full_name[len] == dir_sep) + ns->full_name[len] = 0; + ns->sep = dir_sep; + + p = ns->path = camel_imapp_store_summary_full_to_path(s, ns->full_name, dir_sep); + while (*p) { + if (*p == '/') + *p = '.'; + p++; + } + + return ns; +} + +void camel_imapp_store_summary_namespace_set(CamelIMAPPStoreSummary *s, CamelIMAPPStoreNamespace *ns) +{ + static void namespace_clear(CamelStoreSummary *s); + + d(printf("Setting namesapce to '%s' '%c' -> '%s'\n", ns->full_name, ns->sep, ns->path)); + namespace_clear((CamelStoreSummary *)s); + s->namespace = ns; + camel_store_summary_touch((CamelStoreSummary *)s); +} + +CamelIMAPPStoreNamespace * +camel_imapp_store_summary_namespace_find_path(CamelIMAPPStoreSummary *s, const char *path) +{ + int len; + CamelIMAPPStoreNamespace *ns; + + /* NB: this currently only compares against 1 namespace, in future compare against others */ + ns = s->namespace; + while (ns) { + len = strlen(ns->path); + if (len == 0 + || (strncmp(ns->path, path, len) == 0 + && (path[len] == '/' || path[len] == 0))) + break; + ns = NULL; + } + + /* have a default? */ + return ns; +} + +CamelIMAPPStoreNamespace * +camel_imapp_store_summary_namespace_find_full(CamelIMAPPStoreSummary *s, const char *full) +{ + int len; + CamelIMAPPStoreNamespace *ns; + + /* NB: this currently only compares against 1 namespace, in future compare against others */ + ns = s->namespace; + while (ns) { + len = strlen(ns->full_name); + d(printf("find_full: comparing namespace '%s' to name '%s'\n", ns->full_name, full)); + if (len == 0 + || (strncmp(ns->full_name, full, len) == 0 + && (full[len] == ns->sep || full[len] == 0))) + break; + ns = NULL; + } + + /* have a default? */ + return ns; +} + +static void +namespace_free(CamelStoreSummary *s, CamelIMAPPStoreNamespace *ns) +{ + g_free(ns->path); + g_free(ns->full_name); + g_free(ns); +} + +static void +namespace_clear(CamelStoreSummary *s) +{ + CamelIMAPPStoreSummary *is = (CamelIMAPPStoreSummary *)s; + + if (is->namespace) + namespace_free(s, is->namespace); + is->namespace = NULL; +} + +static CamelIMAPPStoreNamespace * +namespace_load(CamelStoreSummary *s, FILE *in) +{ + CamelIMAPPStoreNamespace *ns; + guint32 sep = '/'; + + ns = g_malloc0(sizeof(*ns)); + if (camel_file_util_decode_string(in, &ns->path) == -1 + || camel_file_util_decode_string(in, &ns->full_name) == -1 + || camel_file_util_decode_uint32(in, &sep) == -1) { + namespace_free(s, ns); + ns = NULL; + } else { + ns->sep = sep; + } + + return ns; +} + +static int +namespace_save(CamelStoreSummary *s, FILE *in, CamelIMAPPStoreNamespace *ns) +{ + if (camel_file_util_encode_string(in, ns->path) == -1 + || camel_file_util_encode_string(in, ns->full_name) == -1 + || camel_file_util_encode_uint32(in, (guint32)ns->sep) == -1) + return -1; + + return 0; +} + +static int +summary_header_load(CamelStoreSummary *s, FILE *in) +{ + CamelIMAPPStoreSummary *is = (CamelIMAPPStoreSummary *)s; + gint32 version, capabilities, count; + + namespace_clear(s); + + if (camel_imapp_store_summary_parent->summary_header_load((CamelStoreSummary *)s, in) == -1 + || camel_file_util_decode_fixed_int32(in, &version) == -1) + return -1; + + is->version = version; + + if (version < CAMEL_IMAPP_STORE_SUMMARY_VERSION_0) { + g_warning("Store summary header version too low"); + return -1; + } + + /* note file format can be expanded to contain more namespaces, but only 1 at the moment */ + if (camel_file_util_decode_fixed_int32(in, &capabilities) == -1 + || camel_file_util_decode_fixed_int32(in, &count) == -1 + || count > 1) + return -1; + + is->capabilities = capabilities; + if (count == 1) { + if ((is->namespace = namespace_load(s, in)) == NULL) + return -1; + } + + return 0; +} + +static int +summary_header_save(CamelStoreSummary *s, FILE *out) +{ + CamelIMAPPStoreSummary *is = (CamelIMAPPStoreSummary *)s; + guint32 count; + + count = is->namespace?1:0; + + /* always write as latest version */ + if (camel_imapp_store_summary_parent->summary_header_save((CamelStoreSummary *)s, out) == -1 + || camel_file_util_encode_fixed_int32(out, CAMEL_IMAPP_STORE_SUMMARY_VERSION) == -1 + || camel_file_util_encode_fixed_int32(out, is->capabilities) == -1 + || camel_file_util_encode_fixed_int32(out, count) == -1) + return -1; + + if (is->namespace && namespace_save(s, out, is->namespace) == -1) + return -1; + + return 0; +} + +static CamelStoreInfo * +store_info_load(CamelStoreSummary *s, FILE *in) +{ + CamelIMAPPStoreInfo *mi; + + mi = (CamelIMAPPStoreInfo *)camel_imapp_store_summary_parent->store_info_load(s, in); + if (mi) { + if (camel_file_util_decode_string(in, &mi->full_name) == -1) { + camel_store_summary_info_free(s, (CamelStoreInfo *)mi); + mi = NULL; + } + } + + return (CamelStoreInfo *)mi; +} + +static int +store_info_save(CamelStoreSummary *s, FILE *out, CamelStoreInfo *mi) +{ + CamelIMAPPStoreInfo *isi = (CamelIMAPPStoreInfo *)mi; + + if (camel_imapp_store_summary_parent->store_info_save(s, out, mi) == -1 + || camel_file_util_encode_string(out, isi->full_name) == -1) + return -1; + + return 0; +} + +static void +store_info_free(CamelStoreSummary *s, CamelStoreInfo *mi) +{ + CamelIMAPPStoreInfo *isi = (CamelIMAPPStoreInfo *)mi; + + g_free(isi->full_name); + camel_imapp_store_summary_parent->store_info_free(s, mi); +} + +static const char * +store_info_string(CamelStoreSummary *s, const CamelStoreInfo *mi, int type) +{ + CamelIMAPPStoreInfo *isi = (CamelIMAPPStoreInfo *)mi; + + /* FIXME: Locks? */ + + g_assert (mi != NULL); + + switch (type) { + case CAMEL_IMAPP_STORE_INFO_FULL_NAME: + return isi->full_name; + default: + return camel_imapp_store_summary_parent->store_info_string(s, mi, type); + } +} + +static void +store_info_set_string(CamelStoreSummary *s, CamelStoreInfo *mi, int type, const char *str) +{ + CamelIMAPPStoreInfo *isi = (CamelIMAPPStoreInfo *)mi; + + g_assert(mi != NULL); + + switch(type) { + case CAMEL_IMAPP_STORE_INFO_FULL_NAME: + d(printf("Set full name %s -> %s\n", isi->full_name, str)); + CAMEL_STORE_SUMMARY_LOCK(s, summary_lock); + g_free(isi->full_name); + isi->full_name = g_strdup(str); + CAMEL_STORE_SUMMARY_UNLOCK(s, summary_lock); + break; + default: + camel_imapp_store_summary_parent->store_info_set_string(s, mi, type, str); + break; + } +} diff --git a/camel/providers/imapp/camel-imapp-store-summary.h b/camel/providers/imapp/camel-imapp-store-summary.h new file mode 100644 index 0000000000..154fe8798e --- /dev/null +++ b/camel/providers/imapp/camel-imapp-store-summary.h @@ -0,0 +1,102 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Copyright (C) 2002 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_IMAPP_STORE_SUMMARY_H +#define _CAMEL_IMAPP_STORE_SUMMARY_H + +#ifdef __cplusplus +extern "C" { +#pragma } +#endif /* __cplusplus */ + +#include <camel/camel-object.h> +#include <camel/camel-store-summary.h> + +#define CAMEL_IMAPP_STORE_SUMMARY(obj) CAMEL_CHECK_CAST (obj, camel_imapp_store_summary_get_type (), CamelIMAPPStoreSummary) +#define CAMEL_IMAPP_STORE_SUMMARY_CLASS(klass) CAMEL_CHECK_CLASS_CAST (klass, camel_imapp_store_summary_get_type (), CamelIMAPPStoreSummaryClass) +#define CAMEL_IS_IMAP_STORE_SUMMARY(obj) CAMEL_CHECK_TYPE (obj, camel_imapp_store_summary_get_type ()) + +typedef struct _CamelIMAPPStoreSummary CamelIMAPPStoreSummary; +typedef struct _CamelIMAPPStoreSummaryClass CamelIMAPPStoreSummaryClass; + +typedef struct _CamelIMAPPStoreInfo CamelIMAPPStoreInfo; + +enum { + CAMEL_IMAPP_STORE_INFO_FULL_NAME = CAMEL_STORE_INFO_LAST, + CAMEL_IMAPP_STORE_INFO_LAST, +}; + +struct _CamelIMAPPStoreInfo { + CamelStoreInfo info; + char *full_name; +}; + +typedef struct _CamelIMAPPStoreNamespace CamelIMAPPStoreNamespace; + +struct _CamelIMAPPStoreNamespace { + char *path; /* display path */ + char *full_name; /* real name */ + char sep; /* directory separator */ +}; + +struct _CamelIMAPPStoreSummary { + CamelStoreSummary summary; + + struct _CamelIMAPPStoreSummaryPrivate *priv; + + /* header info */ + guint32 version; /* version of base part of file */ + guint32 capabilities; + CamelIMAPPStoreNamespace *namespace; /* eventually to be a list */ +}; + +struct _CamelIMAPPStoreSummaryClass { + CamelStoreSummaryClass summary_class; +}; + +CamelType camel_imapp_store_summary_get_type (void); +CamelIMAPPStoreSummary *camel_imapp_store_summary_new (void); + +/* TODO: this api needs some more work, needs to support lists */ +CamelIMAPPStoreNamespace *camel_imapp_store_summary_namespace_new(CamelIMAPPStoreSummary *s, const char *full_name, char dir_sep); +void camel_imapp_store_summary_namespace_set(CamelIMAPPStoreSummary *s, CamelIMAPPStoreNamespace *ns); +CamelIMAPPStoreNamespace *camel_imapp_store_summary_namespace_find_path(CamelIMAPPStoreSummary *s, const char *path); +CamelIMAPPStoreNamespace *camel_imapp_store_summary_namespace_find_full(CamelIMAPPStoreSummary *s, const char *full_name); + +/* converts to/from utf8 canonical nasmes */ +char *camel_imapp_store_summary_full_to_path(CamelIMAPPStoreSummary *s, const char *full_name, char dir_sep); +char *camel_imapp_store_summary_path_to_full(CamelIMAPPStoreSummary *s, const char *path, char dir_sep); + +CamelIMAPPStoreInfo *camel_imapp_store_summary_full_name(CamelIMAPPStoreSummary *s, const char *full_name); +CamelIMAPPStoreInfo *camel_imapp_store_summary_add_from_full(CamelIMAPPStoreSummary *s, const char *full_name, char dir_sep); + +/* a convenience lookup function. always use this if path known */ +char *camel_imapp_store_summary_full_from_path(CamelIMAPPStoreSummary *s, const char *path); + +/* helper macro's */ +#define camel_imapp_store_info_full_name(s, i) (camel_store_info_string((CamelStoreSummary *)s, (const CamelStoreInfo *)i, CAMEL_IMAPP_STORE_INFO_FULL_NAME)) + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* ! _CAMEL_IMAPP_STORE_SUMMARY_H */ diff --git a/camel/providers/imapp/camel-imapp-store.c b/camel/providers/imapp/camel-imapp-store.c new file mode 100644 index 0000000000..36dde917bb --- /dev/null +++ b/camel/providers/imapp/camel-imapp-store.c @@ -0,0 +1,1221 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* camel-imap-store.c : class for a imap store */ + +/* + * Authors: Michael Zucchi <notzed@ximian.com> + * + * Copyright (C) 2000-2002 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 <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <errno.h> + +#include "camel/camel-operation.h" + +#include "camel/camel-stream-buffer.h" +#include "camel/camel-session.h" +#include "camel/camel-exception.h" +#include "camel/camel-url.h" +#include "camel/camel-sasl.h" +#include "camel/camel-data-cache.h" +#include "camel/camel-tcp-stream.h" +#include "camel/camel-tcp-stream-raw.h" +#ifdef HAVE_SSL +#include "camel/camel-tcp-stream-ssl.h" +#endif + +#include "camel-imapp-store-summary.h" +#include "camel-imapp-store.h" +#include "camel-imapp-folder.h" +#include "camel-imapp-engine.h" +#include "camel-imapp-exception.h" +#include "camel-imapp-utils.h" +#include "camel-imapp-driver.h" + +/* Specified in RFC 2060 section 2.1 */ +#define IMAP_PORT 143 + +static CamelStoreClass *parent_class = NULL; + +static void finalize (CamelObject *object); + +static void imap_construct(CamelService *service, CamelSession *session, CamelProvider *provider, CamelURL *url, CamelException *ex); +/* static char *imap_get_name(CamelService *service, gboolean brief);*/ +static gboolean imap_connect (CamelService *service, CamelException *ex); +static gboolean imap_disconnect (CamelService *service, gboolean clean, CamelException *ex); +static GList *imap_query_auth_types (CamelService *service, CamelException *ex); + +static void imap_init_trash (CamelStore *store); +static CamelFolder *imap_get_trash (CamelStore *store, CamelException *ex); + +static CamelFolder *imap_get_folder(CamelStore * store, const char *folder_name, guint32 flags, CamelException * ex); +static CamelFolder *imap_get_inbox (CamelStore *store, CamelException *ex); +static void imap_rename_folder(CamelStore *store, const char *old_name, const char *new_name, CamelException *ex); +static CamelFolderInfo *imap_get_folder_info (CamelStore *store, const char *top, guint32 flags, CamelException *ex); +static void imap_delete_folder(CamelStore *store, const char *folder_name, CamelException *ex); +static void imap_rename_folder(CamelStore *store, const char *old, const char *new, CamelException *ex); +static CamelFolderInfo *imap_create_folder(CamelStore *store, const char *parent_name, const char *folder_name, CamelException *ex); + +static int store_resp_list(CamelIMAPPEngine *ie, guint32 id, void *data); + +/* yet to see if this should go global or not */ +void camel_imapp_store_folder_selected(CamelIMAPPStore *store, CamelIMAPPFolder *folder, CamelIMAPPSelectResponse *select); + +static void +camel_imapp_store_class_init (CamelIMAPPStoreClass *camel_imapp_store_class) +{ + CamelServiceClass *camel_service_class = CAMEL_SERVICE_CLASS(camel_imapp_store_class); + CamelStoreClass *camel_store_class = CAMEL_STORE_CLASS(camel_imapp_store_class); + + parent_class = CAMEL_STORE_CLASS(camel_type_get_global_classfuncs(camel_store_get_type())); + + /* virtual method overload */ + camel_service_class->construct = imap_construct; + /*camel_service_class->get_name = imap_get_name;*/ + camel_service_class->query_auth_types = imap_query_auth_types; + camel_service_class->connect = imap_connect; + camel_service_class->disconnect = imap_disconnect; + + camel_store_class->init_trash = imap_init_trash; + camel_store_class->get_trash = imap_get_trash; + + camel_store_class->get_folder = imap_get_folder; + camel_store_class->get_inbox = imap_get_inbox; + + camel_store_class->create_folder = imap_create_folder; + camel_store_class->rename_folder = imap_rename_folder; + camel_store_class->delete_folder = imap_delete_folder; + camel_store_class->get_folder_info = imap_get_folder_info; +} + +static void +camel_imapp_store_init (gpointer object, gpointer klass) +{ + CamelIMAPPStore *istore = object; +} + +CamelType +camel_imapp_store_get_type (void) +{ + static CamelType camel_imapp_store_type = CAMEL_INVALID_TYPE; + + if (!camel_imapp_store_type) { + camel_imapp_store_type = camel_type_register(CAMEL_STORE_TYPE, + "CamelIMAPPStore", + sizeof (CamelIMAPPStore), + sizeof (CamelIMAPPStoreClass), + (CamelObjectClassInitFunc) camel_imapp_store_class_init, + NULL, + (CamelObjectInitFunc) camel_imapp_store_init, + finalize); + } + + return camel_imapp_store_type; +} + +static void +finalize (CamelObject *object) +{ + CamelIMAPPStore *imap_store = CAMEL_IMAPP_STORE (object); + + /* force disconnect so we dont have it run later, after we've cleaned up some stuff */ + /* SIGH */ + + camel_service_disconnect((CamelService *)imap_store, TRUE, NULL); + + if (imap_store->driver) + camel_object_unref(imap_store->driver); + if (imap_store->cache) + camel_object_unref(imap_store->cache); +} + +static void imap_construct(CamelService *service, CamelSession *session, CamelProvider *provider, CamelURL *url, CamelException *ex) +{ + char *root, *summary; + CamelIMAPPStore *store = (CamelIMAPPStore *)service; + + CAMEL_SERVICE_CLASS (parent_class)->construct (service, session, provider, url, ex); + if (camel_exception_is_set(ex)) + return; + + CAMEL_TRY { + store->summary = camel_imapp_store_summary_new(); + root = camel_session_get_storage_path(service->session, service, ex); + if (root) { + summary = g_build_filename(root, ".ev-store-summary", NULL); + camel_store_summary_set_filename((CamelStoreSummary *)store->summary, summary); + /* FIXME: need to remove params, passwords, etc */ + camel_store_summary_set_uri_base((CamelStoreSummary *)store->summary, service->url); + camel_store_summary_load((CamelStoreSummary *)store->summary); + } + } CAMEL_CATCH(e) { + camel_exception_xfer(ex, e); + } CAMEL_DONE; +} + +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 +connect_to_server (CamelService *service, int ssl_mode, int try_starttls) +/* throws IO exception */ +{ + CamelIMAPPStore *store = CAMEL_IMAPP_STORE (service); + CamelStream * volatile tcp_stream = NULL; + CamelIMAPPStream * volatile imap_stream = NULL; + struct hostent *h = NULL; + int ret, port; + CamelException *ex; + + ex = camel_exception_new(); + CAMEL_TRY { + /* parent class connect initialization */ + CAMEL_SERVICE_CLASS (parent_class)->connect (service, ex); + if (ex->id) + camel_exception_throw_ex(ex); + + h = camel_service_gethost(service, ex); + if (ex->id) + camel_exception_throw_ex(ex); + + port = service->url->port ? service->url->port : IMAP_PORT; + +#ifdef HAVE_SSL + if (camel_url_get_param (service->url, "use_ssl")) { + if (try_starttls) + tcp_stream = camel_tcp_stream_ssl_new_raw (service, service->url->host, STARTTLS_FLAGS); + else { + port = service->url->port ? service->url->port : 995; + tcp_stream = camel_tcp_stream_ssl_new (service, service->url->host, SSL_PORT_FLAGS); + } + } else { + tcp_stream = camel_tcp_stream_raw_new (); + } +#else + tcp_stream = camel_tcp_stream_raw_new (); +#endif /* HAVE_SSL */ + + ret = camel_tcp_stream_connect (CAMEL_TCP_STREAM (tcp_stream), h, port); + camel_free_host (h); + 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 %d): %s"), + service->url->host, port, strerror(errno)); + } + + imap_stream = (CamelIMAPPStream *)camel_imapp_stream_new(tcp_stream); + store->driver = camel_imapp_driver_new(imap_stream); + + camel_object_unref(imap_stream); + camel_object_unref(tcp_stream); + } CAMEL_CATCH(e) { + if (tcp_stream) + camel_object_unref(tcp_stream); + if (imap_stream) + camel_object_unref((CamelObject *)imap_stream); + camel_exception_throw_ex(e); + } CAMEL_DONE; + + camel_exception_free(ex); +} + +#if 0 + +/* leave this stuff out for now */ + + +static struct { + char *value; + int mode; +} ssl_options[] = { + { "", USE_SSL_ALWAYS }, + { "always", USE_SSL_ALWAYS }, + { "when-possible", USE_SSL_WHEN_POSSIBLE }, + { "never", USE_SSL_NEVER }, + { NULL, USE_SSL_NEVER }, +}; + +static gboolean +connect_to_server_wrapper (CamelService *service, CamelException *ex) +{ +#ifdef HAVE_SSL + const char *use_ssl; + int i, ssl_mode; + + use_ssl = camel_url_get_param (service->url, "use_ssl"); + if (use_ssl) { + for (i = 0; ssl_options[i].value; i++) + if (!strcmp (ssl_options[i].value, use_ssl)) + break; + ssl_mode = ssl_options[i].mode; + } else + ssl_mode = USE_SSL_NEVER; + + if (ssl_mode == USE_SSL_ALWAYS) { + /* First try the ssl port */ + if (!connect_to_server (service, ssl_mode, FALSE, ex)) { + if (camel_exception_get_id (ex) == CAMEL_EXCEPTION_SERVICE_UNAVAILABLE) { + /* The ssl port seems to be unavailable, lets try STARTTLS */ + camel_exception_clear (ex); + return connect_to_server (service, ssl_mode, TRUE, ex); + } else { + return FALSE; + } + } + + return TRUE; + } else if (ssl_mode == USE_SSL_WHEN_POSSIBLE) { + /* If the server supports STARTTLS, use it */ + return connect_to_server (service, ssl_mode, TRUE, ex); + } else { + /* User doesn't care about SSL */ + return connect_to_server (service, ssl_mode, FALSE, ex); + } +#else + return connect_to_server (service, USE_SSL_NEVER, FALSE, ex); +#endif +} +#endif + +extern CamelServiceAuthType camel_imapp_password_authtype; +extern CamelServiceAuthType camel_imapp_apop_authtype; + +static GList * +imap_query_auth_types (CamelService *service, CamelException *ex) +{ + /*CamelIMAPPStore *store = CAMEL_IMAPP_STORE (service);*/ + GList *types = NULL; + + types = CAMEL_SERVICE_CLASS (parent_class)->query_auth_types (service, ex); + if (types == NULL) + return NULL; + +#if 0 + if (connect_to_server_wrapper (service, NULL)) { + types = g_list_concat(types, g_list_copy(store->engine->auth)); + imap_disconnect (service, TRUE, NULL); + } else { + camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE, + _("Could not connect to POP server on %s"), + service->url->host); + } +#endif + return types; +} + +#if 0 +static int +try_sasl(CamelIMAPPStore *store, const char *mech, CamelException *ex) +{ + CamelIMAPPStream *stream = store->engine->stream; + unsigned char *line, *resp; + CamelSasl *sasl; + unsigned int len; + int ret; + + sasl = camel_sasl_new("imap", mech, (CamelService *)store); + if (sasl == NULL) { + camel_exception_set (ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE, + _("Unable to connect to POP server.\n" + "No support for requested " + "authentication mechanism.")); + return -1; + } + + if (camel_stream_printf((CamelStream *)stream, "AUTH %s\r\n", mech) == -1) + goto ioerror; + + while (1) { + if (camel_imapp_stream_line(stream, &line, &len) == -1) + goto ioerror; + if (strncmp(line, "+OK", 3) == 0) + break; + if (strncmp(line, "-ERR", 4) == 0) { + camel_exception_setv(ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE, + _("SASL `%s' Login failed: %s"), mech, line); + goto done; + } + /* If we dont get continuation, or the sasl object's run out of work, or we dont get a challenge, + its a protocol error, so fail, and try reset the server */ + if (strncmp(line, "+ ", 2) != 0 + || camel_sasl_authenticated(sasl) + || (resp = camel_sasl_challenge_base64(sasl, line+2, ex)) == NULL) { + camel_stream_printf((CamelStream *)stream, "*\r\n"); + camel_imapp_stream_line(stream, &line, &len); + camel_exception_setv(ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE, + _("SASL Protocol error")); + goto done; + } + + ret = camel_stream_printf((CamelStream *)stream, "%s\r\n", resp); + g_free(resp); + if (ret == -1) + goto ioerror; + + } + camel_object_unref((CamelObject *)sasl); + return 0; + +ioerror: + camel_exception_setv(ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE, + _("I/O Error: %s"), strerror(errno)); +done: + camel_object_unref((CamelObject *)sasl); + return -1; +} + +static gboolean +imap_try_authenticate (CamelService *service, const char *errmsg, CamelException *ex) +{ + CamelIMAPPStore *store = (CamelIMAPPStore *)service; + CamelIMAPPCommand *pcu = NULL, *pcp = NULL; + int status; + + /* override, testing only */ + /*printf("Forcing authmech to 'login'\n"); + service->url->authmech = g_strdup("LOGIN");*/ + + if (!service->url->passwd) { + char *prompt; + + prompt = g_strdup_printf (_("%sPlease enter the POP password for %s@%s"), + errmsg ? errmsg : "", + service->url->user, + service->url->host); + service->url->passwd = camel_session_get_password (camel_service_get_session (service), + prompt, TRUE, service, "password", ex); + g_free (prompt); + if (!service->url->passwd) + return FALSE; + } + + if (!service->url->authmech) { + /* pop engine will take care of pipelining ability */ + pcu = camel_imapp_engine_command_new(store->engine, 0, NULL, NULL, "USER %s\r\n", service->url->user); + pcp = camel_imapp_engine_command_new(store->engine, 0, NULL, NULL, "PASS %s\r\n", service->url->passwd); + } else if (strcmp(service->url->authmech, "+APOP") == 0 && store->engine->apop) { + char *secret, md5asc[33], *d; + unsigned char md5sum[16], *s; + + secret = alloca(strlen(store->engine->apop)+strlen(service->url->passwd)+1); + sprintf(secret, "%s%s", store->engine->apop, service->url->passwd); + md5_get_digest(secret, strlen (secret), md5sum); + + for (s = md5sum, d = md5asc; d < md5asc + 32; s++, d += 2) + sprintf (d, "%.2x", *s); + + pcp = camel_imapp_engine_command_new(store->engine, 0, NULL, NULL, "APOP %s %s\r\n", service->url->user, md5asc); + } else { + CamelServiceAuthType *auth; + GList *l; + + l = store->engine->auth; + while (l) { + auth = l->data; + if (strcmp(auth->authproto, service->url->authmech) == 0) + return try_sasl(store, service->url->authmech, ex) == -1; + l = l->next; + } + + camel_exception_set (ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE, + _("Unable to connect to POP server.\n" + "No support for requested " + "authentication mechanism.")); + return FALSE; + } + + while ((status = camel_imapp_engine_iterate (store->engine, pcp)) > 0) + ; + + if (pcp->state != CAMEL_IMAPP_COMMAND_OK) { + if (status == -1) + camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM, + _("Unable to connect to POP server.\nError sending password: %s"), + errno ? g_strerror (errno) : _("Unknown error")); + else + camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE, + _("Unable to connect to POP server.\nError sending password: %s"), + store->engine->line); + } + camel_imapp_engine_command_free(store->engine, pcp); + + if (pcu) + camel_imapp_engine_command_free(store->engine, pcu); + + return status; +} +#endif + +static gboolean +imap_login(CamelService *service, char *msg) +{ + CamelIMAPPStore *store = (CamelIMAPPStore *)service; + CamelIMAPPCommand * volatile ic = NULL; + + /* override, testing only */ + /*printf("Forcing authmech to 'login'\n"); + service->url->authmech = g_strdup("LOGIN");*/ + + CAMEL_TRY { + if (!service->url->passwd) { + char *prompt; + CamelException *ex = camel_exception_new(); + + prompt = g_strdup_printf (_("%sPlease enter the IMAP password for %s@%s"), + msg?msg:"", + service->url->user, + service->url->host); + service->url->passwd = camel_session_get_password(camel_service_get_session(service), prompt, FALSE, TRUE, service, "password", ex); + g_free (prompt); + if (camel_exception_is_set(ex)) + camel_exception_throw_ex(ex); + } + + if (service->url->authmech) { + CamelSasl *sasl = camel_sasl_new("imap", service->url->authmech, service); + + if (sasl == NULL) + camel_exception_throw(1, "unsupported authentication mechanism: %s", service->url->authmech); + ic = camel_imapp_engine_command_new(store->driver->engine, "AUTHENTICATE", NULL, "AUTHENTICATE %A", sasl); + camel_object_unref((CamelObject *)sasl); + } else { + ic = camel_imapp_engine_command_new(store->driver->engine, "LOGIN", NULL, "LOGIN %s %s", service->url->user, service->url->passwd); + } + + camel_imapp_engine_command_queue(store->driver->engine, ic); + while (camel_imapp_engine_iterate(store->driver->engine, ic) > 0) + ; + + if (ic->status->result != IMAP_OK) + camel_exception_throw(CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE, "Login failed: %s", ic->status->text); + } CAMEL_CATCH(ex) { + if (ic) + camel_imapp_engine_command_free(store->driver->engine, ic); + camel_exception_throw_ex(ex); + } CAMEL_DONE; + + if (ic) + camel_imapp_engine_command_free(store->driver->engine, ic); + + return TRUE; +} + +static void +store_get_pass(CamelIMAPPStore *store) +{ + if (((CamelService *)store)->url->passwd == NULL) { + char *prompt; + CamelException ex; + + camel_exception_init(&ex); + + prompt = g_strdup_printf (_("%sPlease enter the IMAP password for %s@%s"), + store->login_error?store->login_error:"", + ((CamelService *)store)->url->user, + ((CamelService *)store)->url->host); + ((CamelService *)store)->url->passwd = camel_session_get_password(camel_service_get_session((CamelService *)store), + prompt, FALSE, TRUE, (CamelService*)store, "password", &ex); + g_free (prompt); + if (camel_exception_is_set(&ex)) + camel_exception_throw_ex(&ex); + } +} + +static struct _CamelSasl * +store_get_sasl(struct _CamelIMAPPDriver *driver, CamelIMAPPStore *store) +{ + store_get_pass(store); + + if (((CamelService *)store)->url->authmech) + return camel_sasl_new("imap", ((CamelService *)store)->url->authmech, (CamelService *)store); + + return NULL; +} + +static void +store_get_login(struct _CamelIMAPPDriver *driver, char **login, char **pass, CamelIMAPPStore *store) +{ + store_get_pass(store); + + *login = g_strdup(((CamelService *)store)->url->user); + *pass = g_strdup(((CamelService *)store)->url->passwd); +} + +static gboolean +imap_connect (CamelService *service, CamelException *ex) +{ + CamelIMAPPStore *store = (CamelIMAPPStore *)service; + volatile int ret = FALSE; + + CAMEL_TRY { + volatile int retry = TRUE; + + if (store->cache == NULL) { + char *root; + + root = camel_session_get_storage_path(service->session, service, ex); + if (root) { + store->cache = camel_data_cache_new(root, 0, ex); + g_free(root); + if (store->cache) { + /* Default cache expiry - 1 week or not visited in a day */ + camel_data_cache_set_expire_age(store->cache, 60*60*24*7); + camel_data_cache_set_expire_access(store->cache, 60*60*24); + } + } + if (camel_exception_is_set(ex)) + camel_exception_throw_ex(ex); + } + + connect_to_server(service, USE_SSL_NEVER, FALSE); + + camel_imapp_driver_set_sasl_factory(store->driver, (CamelIMAPPSASLFunc)store_get_sasl, store); + camel_imapp_driver_set_login_query(store->driver, (CamelIMAPPLoginFunc)store_get_login, store); + store->login_error = NULL; + + do { + CAMEL_TRY { + if (store->driver->engine->state != IMAP_ENGINE_AUTH) + camel_imapp_driver_login(store->driver); + ret = TRUE; + retry = FALSE; + } CAMEL_CATCH(e) { + g_free(store->login_error); + store->login_error = NULL; + switch (e->id) { + case CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE: + store->login_error = g_strdup_printf("%s\n\n", e->desc); + camel_session_forget_password(service->session, service, "password", ex); + camel_url_set_passwd(service->url, NULL); + break; + default: + camel_exception_throw_ex(e); + break; + } + } CAMEL_DONE; + } while (retry); + } CAMEL_CATCH(e) { + camel_exception_xfer(ex, e); + camel_service_disconnect(service, TRUE, NULL); + ret = FALSE; + } CAMEL_DONE; + + g_free(store->login_error); + store->login_error = NULL; + + return ret; +} + +static gboolean +imap_disconnect (CamelService *service, gboolean clean, CamelException *ex) +{ + CamelIMAPPStore *store = CAMEL_IMAPP_STORE (service); + + /* FIXME: logout */ + + if (!CAMEL_SERVICE_CLASS (parent_class)->disconnect (service, clean, ex)) + return FALSE; + + /* logout/disconnect */ + if (store->driver) { + camel_object_unref(store->driver); + store->driver = NULL; + } + + return TRUE; +} + +static void +imap_init_trash (CamelStore *store) +{ + /* no-op */ + ; +} + +static CamelFolder * +imap_get_trash (CamelStore *store, CamelException *ex) +{ + /* no-op */ + return NULL; +} + +static CamelFolder * +imap_get_folder (CamelStore *store, const char *folder_name, guint32 flags, CamelException *ex) +{ + CamelIMAPPStore *istore = (CamelIMAPPStore *)store; + CamelIMAPPFolder * volatile folder = NULL; + + /* ??? */ + + /* 1. create the folder */ + /* 2. run select? */ + /* 3. update the folder */ + + CAMEL_TRY { + folder = (CamelIMAPPFolder *)camel_imapp_folder_new(store, folder_name); + camel_imapp_driver_select(istore->driver, folder); + } CAMEL_CATCH (e) { + if (folder) { + camel_object_unref(folder); + folder = NULL; + } + camel_exception_xfer(ex, e); + } CAMEL_DONE; + + return (CamelFolder *)folder; +} + +static CamelFolder * +imap_get_inbox(CamelStore *store, CamelException *ex) +{ + camel_exception_setv(ex, 1, "get_inbox::unimplemented"); + + return NULL; +} + +/* 8 bit, string compare */ +static int folders_build_cmp(const void *app, const void *bpp) +{ + struct _list_info *a = *((struct _list_info **)app); + struct _list_info *b = *((struct _list_info **)bpp); + unsigned char *ap = (unsigned char *)(a->name); + unsigned char *bp = (unsigned char *)(b->name); + + printf("qsort, cmp '%s' <> '%s'\n", ap, bp); + + while (*ap && *ap == *bp) { + ap++; + bp++; + } + + if (*ap < *bp) + return -1; + else if (*ap > *bp) + return 1; + return 0; +} + +/* FIXME: this should go via storesummary? */ +static CamelFolderInfo * +folders_build_info(CamelURL *base, struct _list_info *li) +{ + char *path, *full_name, *name; + CamelFolderInfo *fi; + + full_name = imapp_list_get_path(li); + name = strrchr(full_name, '/'); + if (name) + name++; + else + name = full_name; + + path = alloca(strlen(full_name)+2); + sprintf(path, "/%s", full_name); + camel_url_set_path(base, path); + + fi = g_malloc0(sizeof(*fi)); + fi->url = camel_url_to_string(base, CAMEL_URL_HIDE_ALL); + fi->name = g_strdup(name); + fi->full_name = full_name; + fi->path = g_strdup(path); + fi->unread_message_count = -1; + fi->flags = li->flags; + + /* TODO: could look up count here ... */ + /* ?? */ + /*folder = camel_object_bag_get(store->folders, "INBOX");*/ + + return fi; +} + +/* + a + a/b + a/b/c + a/d + b + c/d + +*/ + +/* note, pname is the raw name, not the folderinfo name */ +/* note also this free's as we go, since we never go 'backwards' */ +static CamelFolderInfo * +folders_build_rec(CamelURL *base, GPtrArray *folders, int *ip, CamelFolderInfo *pfi, char *pname) +{ + int plen = 0; + CamelFolderInfo *last = NULL, *first = NULL; + + if (pfi) + plen = strlen(pname); + + for(;(*ip)<(int)folders->len;) { + CamelFolderInfo *fi; + struct _list_info *li; + + li = folders->pdata[*ip]; + printf("checking '%s' is child of '%s'\n", li->name, pname); + + /* is this a child of the parent? */ + if (pfi != NULL + && (strncmp(pname, li->name, strlen(pname)) != 0 + || li->name[plen] != li->separator)) { + printf(" nope\n"); + break; + } + printf(" yep\n"); + + /* is this not an immediate child of the parent? */ +#if 0 + char *p; + if (pfi != NULL + && li->separator != 0 + && (p = strchr(li->name + plen + 1, li->separator)) != NULL) { + if (last == NULL) { + struct _list_info tli; + + tli.flags = CAMEL_FOLDER_NOSELECT|CAMEL_FOLDER_CHILDREN; + tli.separator = li->separator; + tli.name = g_strndup(li->name, p-li->name+1); + fi = folders_build_info(base, &tli); + fi->parent = pfi; + if (pfi && pfi->child == NULL) + pfi->child = fi; + i = folders_build_rec(folders, i, fi, tli.name); + break; + } + } +#endif + + fi = folders_build_info(base, li); + fi->parent = pfi; + if (last != NULL) + last->sibling = fi; + last = fi; + if (first == NULL) + first = fi; + + (*ip)++; + fi->child = folders_build_rec(base, folders, ip, fi, li->name); + imap_free_list(li); + } + + return first; +} + +static void +folder_info_dump(CamelFolderInfo *fi, int depth) +{ + char *s; + + s = alloca(depth+1); + memset(s, ' ', depth); + s[depth] = 0; + while (fi) { + printf("%s%s (%s)\n", s, fi->name, fi->url); + if (fi->child) + folder_info_dump(fi->child, depth+2); + fi = fi->sibling; + } + +} + +static CamelFolderInfo * +imap_get_folder_info(CamelStore *store, const char *top, guint32 flags, CamelException *ex) +{ + CamelIMAPPStore *istore = (CamelIMAPPStore *)store; + CamelFolderInfo * fi= NULL; + char *name; + + if (istore->driver == NULL) { + camel_exception_setv(ex, 1, "Not connected"); + return NULL; + } + + name = (char *)top; + if (name == NULL || name[0] == 0) { + /* namespace? */ + name = ""; + } + + name = ""; + + CAMEL_TRY { + CamelURL *base; + int i; + GPtrArray *folders; + + /* FIXME: subscriptions? lsub? */ + folders = camel_imapp_driver_list(istore->driver, name, flags); + + /* this greatly simplifies the tree algorithm ... but it might + be faster just to use a hashtable to find parents? */ + qsort(folders->pdata, folders->len, sizeof(folders->pdata[0]), folders_build_cmp); + + i = 0; + base = camel_url_copy(((CamelService *)store)->url); + fi = folders_build_rec(base, folders, &i, NULL, NULL); + camel_url_free(base); + g_ptr_array_free(folders, TRUE); + } CAMEL_CATCH(e) { + camel_exception_xfer(ex, e); + } CAMEL_DONE; + + printf("built folder info:\n"); + folder_info_dump(fi, 2); + + return fi; + +#if 0 + if (top == NULL || !g_ascii_strcasecmp(top, "inbox")) { + CamelURL *uri = camel_url_copy(((CamelService *)store)->url); + + camel_url_set_path(uri, "/INBOX"); + fi = g_malloc0(sizeof(*fi)); + fi->url = camel_url_to_string(uri, CAMEL_URL_HIDE_ALL); + camel_url_free(uri); + fi->name = g_strdup("INBOX"); + fi->full_name = g_strdup("INBOX"); + fi->path = g_strdup("/INBOX"); + fi->unread_message_count = -1; + fi->flags = 0; + + folder = camel_object_bag_get(store->folders, "INBOX"); + if (folder) { + /*if (!cflags & FAST)*/ + camel_imapp_driver_update(istore->driver, (CamelIMAPPFolder *)folder); + fi->unread_message_count = camel_folder_get_unread_message_count(folder); + camel_object_unref(folder); + } + } else { + camel_exception_setv(ex, 1, "not implemented"); + } +#endif + return fi; + +#if 0 + istore->pending_list = g_ptr_array_new(); + + CAMEL_TRY { + ic = camel_imapp_engine_command_new(istore->driver->engine, "LIST", NULL, "LIST \"\" %f", top); + camel_imapp_engine_command_queue(istore->driver->engine, ic); + while (camel_imapp_engine_iterate(istore->driver->engine, ic) > 0) + ; + + if (ic->status->result != IMAP_OK) + camel_exception_throw(1, "list failed: %s", ic->status->text); + } CAMEL_CATCH (e) { + camel_exception_xfer(ex, e); + } CAMEL_DONE; + + camel_imapp_engine_command_free(istore->driver->engine, ic); + + printf("got folder list:\n"); + for (i=0;i<(int)istore->pending_list->len;i++) { + struct _list_info *linfo = istore->pending_list->pdata[i]; + + printf("%s (%c)\n", linfo->name, linfo->separator); + imap_free_list(linfo); + } + istore->pending_list = NULL; + + return NULL; +#endif +} + +static void +imap_delete_folder(CamelStore *store, const char *folder_name, CamelException *ex) +{ + camel_exception_setv(ex, 1, "delete_folder::unimplemented"); +} + +static void +imap_rename_folder(CamelStore *store, const char *old, const char *new, CamelException *ex) +{ + camel_exception_setv(ex, 1, "rename_folder::unimplemented"); +} + +static CamelFolderInfo * +imap_create_folder(CamelStore *store, const char *parent_name, const char *folder_name, CamelException *ex) +{ + camel_exception_setv(ex, 1, "create_folder::unimplemented"); + return NULL; +} + +/* ********************************************************************** */ + +static int store_resp_list(CamelIMAPPEngine *ie, guint32 id, void *data) +{ + struct _list_info *linfo; + CamelIMAPPStore *istore = data; + + linfo = imap_parse_list(ie->stream); + printf("store list: '%s' ('%c')\n", linfo->name, linfo->separator); + if (istore->pending_list) + g_ptr_array_add(istore->pending_list, linfo); + else { + g_warning("unexpected list response\n"); + imap_free_list(linfo); + } + + return camel_imapp_engine_skip(ie); +} + +#if 0 +static int store_resp_fetch(CamelIMAPPEngine *ie, guint32 id, void *data) +{ + struct _fetch_info *finfo; + CamelIMAPPStore *istore = data; + CamelMessageInfo *info; + struct _pending_fetch *pending; + + finfo = imap_parse_fetch(ie->stream); + if (istore->selected) { + if ((finfo->got & FETCH_UID) == 0) { + printf("didn't get uid in fetch response?\n"); + } else { + info = camel_folder_summary_index(((CamelFolder *)istore->selected)->summary, id-1); + /* exists, check/update */ + if (info) { + if (strcmp(finfo->uid, camel_message_info_uid(info)) != 0) { + printf("summary at index %d has uid %s expected %s\n", id, camel_message_info_uid(info), finfo->uid); + /* uid mismatch??? try do it based on uid instead? try to reorder? i dont know? */ + camel_folder_summary_info_free(((CamelFolder *)istore->selected)->summary, info); + info = camel_folder_summary_uid(((CamelFolder *)istore->selected)->summary, finfo->uid); + } + } + + if (info) { + if (finfo->got & (FETCH_FLAGS)) { + printf("updating flags for uid '%s'\n", finfo->uid); + info->flags = finfo->flags; + camel_folder_change_info_change_uid(istore->selected->changes, finfo->uid); + } + if (finfo->got & FETCH_MINFO) { + printf("got envelope unexpectedly?\n"); + } + /* other things go here, like body fetches */ + } else { + pending = g_hash_table_lookup(istore->pending_fetch_table, finfo->uid); + + /* we need to create a new info, we only care about flags and minfo */ + + if (pending) + info = pending->info; + else { + info = camel_folder_summary_info_new(((CamelFolder *)istore->selected)->summary); + camel_message_info_set_uid(info, g_strdup(finfo->uid)); + } + + if (finfo->got & FETCH_FLAGS) + info->flags = finfo->flags; + + if (finfo->got & FETCH_MINFO) { + /* if we only use ENVELOPE? */ + camel_message_info_set_subject(info, g_strdup(camel_message_info_subject(finfo->minfo))); + camel_message_info_set_from(info, g_strdup(camel_message_info_from(finfo->minfo))); + camel_message_info_set_to(info, g_strdup(camel_message_info_to(finfo->minfo))); + camel_message_info_set_cc(info, g_strdup(camel_message_info_cc(finfo->minfo))); + info->date_sent = finfo->minfo->date_sent; + camel_folder_summary_add(((CamelFolder *)istore->selected)->summary, info); + camel_folder_change_info_add_uid(istore->selected->changes, finfo->uid); + if (pending) { + e_dlist_remove((EDListNode *)pending); + g_hash_table_remove(istore->pending_fetch_table, finfo->uid); + /*e_memchunk_free(istore->pending_fetch_chunks, pending);*/ + } + } else if (finfo->got & FETCH_HEADER) { + /* if we only use HEADER? */ + CamelMimeParser *mp; + + if (pending == NULL) + camel_folder_summary_info_free(((CamelFolder *)istore->selected)->summary, info); + mp = camel_mime_parser_new(); + camel_mime_parser_init_with_stream(mp, finfo->header); + info = camel_folder_summary_info_new_from_parser(((CamelFolder *)istore->selected)->summary, mp); + camel_object_unref(mp); + camel_message_info_set_uid(info, g_strdup(finfo->uid)); + + camel_folder_summary_add(((CamelFolder *)istore->selected)->summary, info); + camel_folder_change_info_add_uid(istore->selected->changes, finfo->uid); + if (pending) { + /* FIXME: use a dlist */ + e_dlist_remove((EDListNode *)pending); + g_hash_table_remove(istore->pending_fetch_table, camel_message_info_uid(pending->info)); + camel_folder_summary_info_free(((CamelFolder *)istore->selected)->summary, pending->info); + /*e_memchunk_free(istore->pending_fetch_chunks, pending);*/ + } + } else if (finfo->got & FETCH_FLAGS) { + if (pending == NULL) { + pending = e_memchunk_alloc(istore->pending_fetch_chunks); + pending->info = info; + g_hash_table_insert(istore->pending_fetch_table, (char *)camel_message_info_uid(info), pending); + e_dlist_addtail(&istore->pending_fetch_list, (EDListNode *)pending); + } + } else { + if (pending == NULL) + camel_folder_summary_info_free(((CamelFolder *)istore->selected)->summary, info); + printf("got unexpected fetch response?\n"); + imap_dump_fetch(finfo); + } + } + } + } else { + printf("unexpected fetch response, no folder selected?\n"); + } + /*imap_dump_fetch(finfo);*/ + imap_free_fetch(finfo); + + return camel_imapp_engine_skip(ie); +} +#endif + +/* ********************************************************************** */ + +/* should be moved to imapp-utils? + stuff in imapp-utils should be moved to imapp-parse? */ + +/* ********************************************************************** */ + +#if 0 +void +camel_imapp_store_folder_selected(CamelIMAPPStore *store, CamelIMAPPFolder *folder, CamelIMAPPSelectResponse *select) +{ + CamelIMAPPCommand * volatile ic = NULL; + CamelIMAPPStore *istore = (CamelIMAPPStore *)store; + int i; + struct _uidset_state ss; + GPtrArray *fetch; + CamelMessageInfo *info; + struct _pending_fetch *fw, *fn; + + printf("imap folder selected\n"); + + if (select->uidvalidity == folder->uidvalidity + && select->exists == folder->exists + && select->recent == folder->recent + && select->unseen == folder->unseen) { + /* no work to do? */ + return; + } + + istore->pending_fetch_table = g_hash_table_new(g_str_hash, g_str_equal); + istore->pending_fetch_chunks = e_memchunk_new(256, sizeof(struct _pending_fetch)); + + /* perform an update - flags first (and see what we have) */ + CAMEL_TRY { + ic = camel_imapp_engine_command_new(istore->engine, "FETCH", NULL, "FETCH 1:%d (UID FLAGS)", select->exists); + camel_imapp_engine_command_queue(istore->engine, ic); + while (camel_imapp_engine_iterate(istore->engine, ic) > 0) + ; + + if (ic->status->result != IMAP_OK) + camel_exception_throw(1, "fetch failed: %s", ic->status->text); + + /* pending_fetch_list now contains any new messages */ + /* FIXME: how do we work out no-longer present messages? */ + printf("now fetching info for messages?\n"); + uidset_init(&ss, store->engine); + ic = camel_imapp_engine_command_new(istore->engine, "FETCH", NULL, "UID FETCH "); + fw = (struct _pending_fetch *)istore->pending_fetch_list.head; + fn = fw->next; + while (fn) { + info = fw->info; + /* if the uid set fills, then flush the command out */ + if (uidset_add(&ss, ic, camel_message_info_uid(info)) + || (fn->next == NULL && uidset_done(&ss, ic))) { + camel_imapp_engine_command_add(istore->engine, ic, " (FLAGS RFC822.HEADER)"); + camel_imapp_engine_command_queue(istore->engine, ic); + while (camel_imapp_engine_iterate(istore->engine, ic) > 0) + ; + if (ic->status->result != IMAP_OK) + camel_exception_throw(1, "fetch failed: %s", ic->status->text); + /* if not end ... */ + camel_imapp_engine_command_free(istore->engine, ic); + ic = camel_imapp_engine_command_new(istore->engine, "FETCH", NULL, "UID FETCH "); + } + fw = fn; + fn = fn->next; + } + + printf("The pending list should now be empty: %s\n", e_dlist_empty(&istore->pending_fetch_list)?"TRUE":"FALSE"); + for (i=0;i<10;i++) { + info = camel_folder_summary_index(((CamelFolder *)istore->selected)->summary, i); + if (info) { + printf("message info [%d] =\n", i); + camel_message_info_dump(info); + camel_folder_summary_info_free(((CamelFolder *)istore->selected)->summary, info); + } + } + } CAMEL_CATCH (e) { + /* FIXME: cleanup */ + camel_exception_throw_ex(e); + } CAMEL_DONE; + + g_hash_table_destroy(istore->pending_fetch_table); + istore->pending_fetch_table = NULL; + e_memchunk_destroy(istore->pending_fetch_chunks); + + camel_imapp_engine_command_free(istore->engine, ic); +} +#endif + +#if 0 +/*char *uids[] = {"1", "2", "4", "5", "6", "7", "9", "11", "12", 0};*/ +/*char *uids[] = {"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", 0};*/ +char *uids[] = {"1", "3", "5", "7", "9", "11", "12", "13", "14", "15", "20", "21", "24", "25", "26", 0}; + +void +uidset_test(CamelIMAPPEngine *ie) +{ + struct _uidset_state ss; + CamelIMAPPCommand *ic; + int i; + + /*ic = camel_imapp_engine_command_new(ie, 0, "FETCH", NULL, "FETCH ");*/ + uidset_init(&ss, 0, 0); + for (i=0;uids[i];i++) { + if (uidset_add(&ss, uids[i])) { + printf("\n[%d] flushing uids\n", i); + } + } + + if (uidset_done(&ss)) { + printf("\nflushing uids\n"); + } +} +#endif diff --git a/camel/providers/imapp/camel-imapp-store.h b/camel/providers/imapp/camel-imapp-store.h new file mode 100644 index 0000000000..97b7afd1d1 --- /dev/null +++ b/camel/providers/imapp/camel-imapp-store.h @@ -0,0 +1,77 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* camel-imap-store.h : class for an imap store */ + +/* + * Authors: Michael Zucchi <notzed@ximian.com> + * + * Copyright (C) 2002 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 + */ + + +#ifndef CAMEL_IMAPP_STORE_H +#define CAMEL_IMAPP_STORE_H 1 + +#ifdef __cplusplus +extern "C" { +#pragma } +#endif /* __cplusplus }*/ + +#include <camel/camel-types.h> +#include <camel/camel-store.h> +#include "camel-imapp-driver.h" +#include "e-util/e-memory.h" + +#define CAMEL_IMAPP_STORE_TYPE (camel_imapp_store_get_type ()) +#define CAMEL_IMAPP_STORE(obj) (CAMEL_CHECK_CAST((obj), CAMEL_IMAPP_STORE_TYPE, CamelIMAPPStore)) +#define CAMEL_IMAPP_STORE_CLASS(k) (CAMEL_CHECK_CLASS_CAST ((k), CAMEL_IMAPP_STORE_TYPE, CamelIMAPPStoreClass)) +#define CAMEL_IS_IMAP_STORE(o) (CAMEL_CHECK_TYPE((o), CAMEL_IMAPP_STORE_TYPE)) + +struct _pending_fetch { + struct _pending_fetch *next; + struct _pending_fetch *prev; + + CamelMessageInfo *info; +}; + +typedef struct { + CamelStore parent_object; + + struct _CamelIMAPPStoreSummary *summary; /* in-memory list of folders */ + struct _CamelIMAPPDriver *driver; /* IMAP processing engine */ + struct _CamelDataCache *cache; + + /* if we had a login error, what to show to user */ + char *login_error; + + GPtrArray *pending_list; +} CamelIMAPPStore; + +typedef struct { + CamelStoreClass parent_class; + +} CamelIMAPPStoreClass; + +/* Standard Camel function */ +CamelType camel_imapp_store_get_type (void); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* CAMEL_IMAPP_STORE_H */ + + diff --git a/camel/providers/imapp/camel-imapp-stream.c b/camel/providers/imapp/camel-imapp-stream.c new file mode 100644 index 0000000000..a9567bf4f8 --- /dev/null +++ b/camel/providers/imapp/camel-imapp-stream.c @@ -0,0 +1,761 @@ +/* -*- 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-imapp-stream.h" +#include "camel-imapp-exception.h" + +#define t(x) +#define io(x) x + +static void setup_table(void); + +static CamelObjectClass *parent_class = NULL; + +/* Returns the class for a CamelStream */ +#define CS_CLASS(so) CAMEL_IMAPP_STREAM_CLASS(CAMEL_OBJECT_GET_CLASS(so)) + +#define CAMEL_IMAPP_STREAM_SIZE (4096) +#define CAMEL_IMAPP_STREAM_TOKEN (4096) /* maximum token size */ + +static int +stream_fill(CamelIMAPPStream *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_IMAPP_STREAM_SIZE - (is->end - is->buf)); + if (left > 0) { + is->end += left; + io(printf("camel_imapp_read: buffer is '%.*s'\n", is->end - is->ptr, is->ptr)); + return is->end - is->ptr; + } else { + io(printf("camel_imapp_read: -1\n")); + return -1; + } + } + + printf("camel_imapp_read: 0\n"); + + return 0; +} + +static ssize_t +stream_read(CamelStream *stream, char *buffer, size_t n) +{ + CamelIMAPPStream *is = (CamelIMAPPStream *)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; + } + + is->literal -= max; + + return max; +} + +static ssize_t +stream_write(CamelStream *stream, const char *buffer, size_t n) +{ + CamelIMAPPStream *is = (CamelIMAPPStream *)stream; + + 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) +{ + CamelIMAPPStream *is = (CamelIMAPPStream *)stream; + + return is->literal == 0; +} + +static int +stream_reset(CamelStream *stream) +{ + /* nop? reset literal mode? */ + return 0; +} + +static void +camel_imapp_stream_class_init (CamelStreamClass *camel_imapp_stream_class) +{ + CamelStreamClass *camel_stream_class = (CamelStreamClass *)camel_imapp_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_imapp_stream_init(CamelIMAPPStream *is, CamelIMAPPStreamClass *isclass) +{ + /* +1 is room for appending a 0 if we need to for a token */ + is->ptr = is->end = is->buf = g_malloc(CAMEL_IMAPP_STREAM_SIZE+1); + is->tokenptr = is->tokenbuf = g_malloc(CAMEL_IMAPP_STREAM_SIZE+1); + is->tokenend = is->tokenbuf + CAMEL_IMAPP_STREAM_SIZE; +} + +static void +camel_imapp_stream_finalise(CamelIMAPPStream *is) +{ + g_free(is->buf); + if (is->source) + camel_object_unref((CamelObject *)is->source); +} + +CamelType +camel_imapp_stream_get_type (void) +{ + static CamelType camel_imapp_stream_type = CAMEL_INVALID_TYPE; + + if (camel_imapp_stream_type == CAMEL_INVALID_TYPE) { + setup_table(); + camel_imapp_stream_type = camel_type_register( camel_stream_get_type(), + "CamelIMAPPStream", + sizeof( CamelIMAPPStream ), + sizeof( CamelIMAPPStreamClass ), + (CamelObjectClassInitFunc) camel_imapp_stream_class_init, + NULL, + (CamelObjectInitFunc) camel_imapp_stream_init, + (CamelObjectFinalizeFunc) camel_imapp_stream_finalise ); + } + + return camel_imapp_stream_type; +} + +/** + * camel_imapp_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_imapp_stream_new(CamelStream *source) +{ + CamelIMAPPStream *is; + + is = (CamelIMAPPStream *)camel_object_new(camel_imapp_stream_get_type ()); + camel_object_ref((CamelObject *)source); + is->source = source; + + return (CamelStream *)is; +} + + +/* + From rfc2060 + +ATOM_CHAR ::= <any CHAR except atom_specials> + +atom_specials ::= "(" / ")" / "{" / SPACE / CTL / list_wildcards / + quoted_specials + +CHAR ::= <any 7-bit US-ASCII character except NUL, + 0x01 - 0x7f> + +CTL ::= <any ASCII control character and DEL, + 0x00 - 0x1f, 0x7f> + +SPACE ::= <ASCII SP, space, 0x20> + +list_wildcards ::= "%" / "*" + +quoted_specials ::= <"> / "\" +*/ + +static unsigned char imap_specials[256] = { +/* 00 */0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +/* 10 */0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +/* 20 */0, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, +/* 30 */1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, +/* 40 */1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, +/* 50 */1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, +/* 60 */1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, +/* 70 */1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +}; + +#define imap_is_atom(c) ((imap_specials[(c)&0xff] & 0x01) != 0) +#define imap_is_simple(c) ((imap_specials[(c)&0xff] & 0x02) != 0) +#define imap_not_id(c) ((imap_specials[(c)&0xff] & 0x04) != 0) + +/* could be pregenerated, but this is cheap */ +static struct { + unsigned char *chars; + unsigned char mask; +} is_masks[] = { + { "\n*()[]+", 2 }, + { " \r\n()[]+", 4 }, +}; + +static void setup_table(void) +{ + int i; + unsigned char *p, c; + + for (i=0;i<(int)(sizeof(is_masks)/sizeof(is_masks[0]));i++) { + p = is_masks[i].chars; + while ((c = *p++)) + imap_specials[c] |= is_masks[i].mask; + } +} + +#if 0 + +static int +skip_ws(CamelIMAPPStream *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_imapp_stream_atom(CamelIMAPPStream *is, unsigned char **data, unsigned int *lenp) +{ + unsigned char *p, c; + + /* this is only 'approximate' atom */ + switch(camel_imapp_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_imapp_stream_astring(CamelIMAPPStream *is, unsigned char **data) +{ + unsigned char *p, *start; + unsigned int len, inlen; + + switch(camel_imapp_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_IMAPP_STREAM_TOKEN) { + camel_exception_throw(1, "astring: literal too long"); + printf("astring too long\n"); + return IMAP_TOK_PROTOCOL; + } + p = is->tokenptr; + camel_imapp_stream_set_literal(is, len); + do { + len = camel_imapp_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_imapp_stream_nstring(CamelIMAPPStream *is, unsigned char **data) +{ + unsigned char *p, *start; + unsigned int len, inlen; + + switch(camel_imapp_stream_token(is, data, &len)) { + case IMAP_TOK_STRING: + return 0; + case IMAP_TOK_LITERAL: + /* FIXME: just grow buffer */ + if (len >= CAMEL_IMAPP_STREAM_TOKEN) { + camel_exception_throw(1, "nstring: literal too long"); + return IMAP_TOK_PROTOCOL; + } + p = is->tokenptr; + camel_imapp_stream_set_literal(is, len); + do { + len = camel_imapp_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_imapp_stream_nstring_stream(CamelIMAPPStream *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_imapp_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_imapp_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_imapp_stream_number(CamelIMAPPStream *is) +{ + unsigned char *token; + unsigned int len; + + if (camel_imapp_stream_token(is, &token, &len) != IMAP_TOK_INT) { + camel_exception_throw(1, "expecting number"); + return 0; + } + + return strtoul(token, 0, 10); +} + +int +camel_imapp_stream_text(CamelIMAPPStream *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_imapp_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_imapp_token_t +/* throws IO,PARSE exception */ +camel_imapp_stream_token(CamelIMAPPStream *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 (imap_is_simple(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_IMAPP_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_IMAPP_STREAM_TOKEN - 1; + digits = isdigit(c); + *o++ = c; + while (1) { + while (p < e) { + c = *p++; + /*if (strchr(" \r\n*()[]+", c) != NULL) {*/ + if (imap_not_id(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_imapp_stream_ungettoken(CamelIMAPPStream *is, camel_imapp_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_imapp_stream_gets(CamelIMAPPStream *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_imapp_stream_set_literal(CamelIMAPPStream *is, unsigned int literal) +{ + is->literal = literal; +} + +/* returns -1 on erorr, 0 if last data, >0 if more data left */ +int camel_imapp_stream_getl(CamelIMAPPStream *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; +} diff --git a/camel/providers/imapp/camel-imapp-stream.h b/camel/providers/imapp/camel-imapp-stream.h new file mode 100644 index 0000000000..802c018672 --- /dev/null +++ b/camel/providers/imapp/camel-imapp-stream.h @@ -0,0 +1,90 @@ +/* + * 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_IMAPP_STREAM_H +#define _CAMEL_IMAPP_STREAM_H + +#include <camel/camel-stream.h> + +#define CAMEL_IMAPP_STREAM(obj) CAMEL_CHECK_CAST (obj, camel_imapp_stream_get_type (), CamelIMAPPStream) +#define CAMEL_IMAPP_STREAM_CLASS(klass) CAMEL_CHECK_CLASS_CAST (klass, camel_imapp_stream_get_type (), CamelIMAPPStreamClass) +#define CAMEL_IS_IMAP_STREAM(obj) CAMEL_CHECK_TYPE (obj, camel_imapp_stream_get_type ()) + +typedef struct _CamelIMAPPStreamClass CamelIMAPPStreamClass; +typedef struct _CamelIMAPPStream CamelIMAPPStream; + +typedef enum { + IMAP_TOK_PROTOCOL = -2, + IMAP_TOK_ERROR = -1, + IMAP_TOK_TOKEN = 256, + IMAP_TOK_STRING, + IMAP_TOK_INT, + IMAP_TOK_LITERAL, +} camel_imapp_token_t; + +struct _CamelIMAPPStream { + CamelStream parent; + + CamelStream *source; + + /*int state;*/ + unsigned char *buf, *ptr, *end; + unsigned int literal; + + unsigned int unget; + camel_imapp_token_t unget_tok; + unsigned char *unget_token; + unsigned int unget_len; + + unsigned char *tokenbuf, *tokenptr, *tokenend; +}; + +struct _CamelIMAPPStreamClass { + CamelStreamClass parent_class; +}; + +CamelType camel_imapp_stream_get_type (void); + +CamelStream *camel_imapp_stream_new (CamelStream *source); + +camel_imapp_token_t camel_imapp_stream_token (CamelIMAPPStream *is, unsigned char **start, unsigned int *len); /* throws IO,PARSE exception */ +void camel_imapp_stream_ungettoken (CamelIMAPPStream *is, camel_imapp_token_t tok, unsigned char *token, unsigned int len); + +void camel_imapp_stream_set_literal (CamelIMAPPStream *is, unsigned int literal); +int camel_imapp_stream_gets (CamelIMAPPStream *is, unsigned char **start, unsigned int *len); +int camel_imapp_stream_getl (CamelIMAPPStream *is, unsigned char **start, unsigned int *len); + +/* all throw IO,PARSE exceptions */ + +/* gets an atom, upper-cases */ +int camel_imapp_stream_atom (CamelIMAPPStream *is, unsigned char **start, unsigned int *len); +/* gets an atom or string */ +int camel_imapp_stream_astring (CamelIMAPPStream *is, unsigned char **start); +/* gets a NIL or a string, start==NULL if NIL */ +int camel_imapp_stream_nstring (CamelIMAPPStream *is, unsigned char **start); +/* gets a NIL or string into a stream, stream==NULL if NIL */ +int camel_imapp_stream_nstring_stream(CamelIMAPPStream *is, CamelStream **stream); +/* gets 'text' */ +int camel_imapp_stream_text (CamelIMAPPStream *is, unsigned char **text); + +/* gets a 'number' */ +guint32 camel_imapp_stream_number(CamelIMAPPStream *is); + +#endif /* ! _CAMEL_IMAPP_STREAM_H */ diff --git a/camel/providers/imapp/camel-imapp-summary.c b/camel/providers/imapp/camel-imapp-summary.c new file mode 100644 index 0000000000..a0df21bcb0 --- /dev/null +++ b/camel/providers/imapp/camel-imapp-summary.c @@ -0,0 +1,166 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Copyright(C) 2000 Ximian Inc. + * + * Authors: + * Michael Zucchi <notzed@ximian.com> + * Dan Winship <danw@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 <sys/stat.h> +#include <sys/uio.h> +#include <unistd.h> +#include <errno.h> +#include <string.h> +#include <stdlib.h> + +#include "camel-imapp-summary.h" +#include <camel/camel-file-utils.h> + +#define CAMEL_IMAPP_SUMMARY_VERSION (0x1000) + +static int summary_header_load(CamelFolderSummary *, FILE *); +static int summary_header_save(CamelFolderSummary *, FILE *); + +static CamelMessageInfo *message_info_load(CamelFolderSummary *s, FILE *in); +static int message_info_save(CamelFolderSummary *s, FILE *out, CamelMessageInfo *info); + +static void camel_imapp_summary_class_init(CamelIMAPPSummaryClass *klass); +static void camel_imapp_summary_init (CamelIMAPPSummary *obj); + +static CamelFolderSummaryClass *camel_imapp_summary_parent; + +CamelType +camel_imapp_summary_get_type(void) +{ + static CamelType type = CAMEL_INVALID_TYPE; + + if (type == CAMEL_INVALID_TYPE) { + type = camel_type_register( + camel_folder_summary_get_type(), "CamelIMAPPSummary", + sizeof(CamelIMAPPSummary), + sizeof(CamelIMAPPSummaryClass), + (CamelObjectClassInitFunc) camel_imapp_summary_class_init, + NULL, + (CamelObjectInitFunc) camel_imapp_summary_init, + NULL); + } + + return type; +} + +static void +camel_imapp_summary_class_init(CamelIMAPPSummaryClass *klass) +{ + CamelFolderSummaryClass *cfs_class =(CamelFolderSummaryClass *) klass; + + camel_imapp_summary_parent = CAMEL_FOLDER_SUMMARY_CLASS(camel_type_get_global_classfuncs(camel_folder_summary_get_type())); + + cfs_class->summary_header_load = summary_header_load; + cfs_class->summary_header_save = summary_header_save; + cfs_class->message_info_load = message_info_load; + cfs_class->message_info_save = message_info_save; +} + +static void +camel_imapp_summary_init(CamelIMAPPSummary *obj) +{ + CamelFolderSummary *s =(CamelFolderSummary *)obj; + + /* subclasses need to set the right instance data sizes */ + s->message_info_size = sizeof(CamelIMAPPMessageInfo); + s->content_info_size = sizeof(CamelMessageContentInfo); + + /* and a unique file version */ + s->version += CAMEL_IMAPP_SUMMARY_VERSION; +} + +/** + * camel_imapp_summary_new: + * @filename: the file to store the summary in. + * + * This will create a new CamelIMAPPSummary object and read in the + * summary data from disk, if it exists. + * + * Return value: A new CamelIMAPPSummary object. + **/ +CamelFolderSummary * +camel_imapp_summary_new(void) +{ + CamelFolderSummary *summary = CAMEL_FOLDER_SUMMARY(camel_object_new(camel_imapp_summary_get_type())); + + return summary; +} + + +static int +summary_header_load(CamelFolderSummary *s, FILE *in) +{ + CamelIMAPPSummary *ims = CAMEL_IMAPP_SUMMARY(s); + + if (camel_imapp_summary_parent->summary_header_load(s, in) == -1) + return -1; + + return camel_file_util_decode_uint32(in, &ims->uidvalidity); +} + +static int +summary_header_save(CamelFolderSummary *s, FILE *out) +{ + CamelIMAPPSummary *ims = CAMEL_IMAPP_SUMMARY(s); + + if (camel_imapp_summary_parent->summary_header_save(s, out) == -1) + return -1; + + return camel_file_util_encode_uint32(out, ims->uidvalidity); +} + + +static CamelMessageInfo * +message_info_load(CamelFolderSummary *s, FILE *in) +{ + CamelMessageInfo *info; + CamelIMAPPMessageInfo *iinfo; + + info = camel_imapp_summary_parent->message_info_load(s, in); + if (info) { + iinfo =(CamelIMAPPMessageInfo *)info; + + if (camel_file_util_decode_uint32(in, &iinfo->server_flags) == -1) + goto error; + } + + return info; +error: + camel_folder_summary_info_free(s, info); + return NULL; +} + +static int +message_info_save(CamelFolderSummary *s, FILE *out, CamelMessageInfo *info) +{ + CamelIMAPPMessageInfo *iinfo =(CamelIMAPPMessageInfo *)info; + + if (camel_imapp_summary_parent->message_info_save(s, out, info) == -1) + return -1; + + return camel_file_util_encode_uint32(out, iinfo->server_flags); +} diff --git a/camel/providers/imapp/camel-imapp-summary.h b/camel/providers/imapp/camel-imapp-summary.h new file mode 100644 index 0000000000..e9783625f4 --- /dev/null +++ b/camel/providers/imapp/camel-imapp-summary.h @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2000 Ximian Inc. + * + * Authors: + * Michael Zucchi <notzed@ximian.com> + * Dan Winship <danw@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_IMAPP_SUMMARY_H +#define _CAMEL_IMAPP_SUMMARY_H + +#include <camel/camel-folder-summary.h> +#include <camel/camel-exception.h> + +#define CAMEL_IMAPP_SUMMARY(obj) CAMEL_CHECK_CAST (obj, camel_imapp_summary_get_type (), CamelIMAPPSummary) +#define CAMEL_IMAPP_SUMMARY_CLASS(klass) CAMEL_CHECK_CLASS_CAST (klass, camel_imapp_summary_get_type (), CamelIMAPPSummaryClass) +#define CAMEL_IS_IMAPP_SUMMARY(obj) CAMEL_CHECK_TYPE (obj, camel_imapp_summary_get_type ()) + +#define CAMEL_IMAPP_SERVER_FLAGS (CAMEL_MESSAGE_ANSWERED | \ + CAMEL_MESSAGE_DELETED | \ + CAMEL_MESSAGE_DRAFT | \ + CAMEL_MESSAGE_FLAGGED | \ + CAMEL_MESSAGE_SEEN) + +#define CAMEL_IMAPP_MESSAGE_RECENT (1 << 8) + +typedef struct _CamelIMAPPSummaryClass CamelIMAPPSummaryClass; +typedef struct _CamelIMAPPSummary CamelIMAPPSummary; + +typedef struct _CamelIMAPPMessageInfo { + CamelMessageInfo info; + + guint32 server_flags; +} CamelIMAPPMessageInfo; + +struct _CamelIMAPPSummary { + CamelFolderSummary parent; + + guint32 uidvalidity; +}; + +struct _CamelIMAPPSummaryClass { + CamelFolderSummaryClass parent_class; + +}; + +CamelType camel_imapp_summary_get_type (void); +CamelFolderSummary *camel_imapp_summary_new (void); + +#endif /* ! _CAMEL_IMAPP_SUMMARY_H */ + diff --git a/camel/providers/imapp/camel-imapp-utils.c b/camel/providers/imapp/camel-imapp-utils.c new file mode 100644 index 0000000000..57935a81e8 --- /dev/null +++ b/camel/providers/imapp/camel-imapp-utils.c @@ -0,0 +1,1339 @@ + +#include <ctype.h> +#include <errno.h> +#include <string.h> + +#include <camel/camel-folder-summary.h> +#include <camel/camel-store.h> +#include <camel/camel-utf8.h> + +#include "camel-imapp-folder.h" +#include "camel-imapp-stream.h" +#include "camel-imapp-utils.h" +#include "camel-imapp-exception.h" +#include "camel-imapp-engine.h" +#include "e-util/e-memory.h" + +/* high-level parser state */ +#define p(x) +/* debug */ +#define d(x) + +/* ANSI-C code produced by gperf version 2.7 */ +/* Command-line: gperf -H imap_hash -N imap_tokenise -L ANSI-C -o -t -k1,$ imap-tokens.txt */ +struct _imap_keyword { char *name; enum _imap_id id; }; +/* + gperf input file + best hash generated using: gperf -o -s-2 -k1,'$' -t -H imap_hash -N imap_tokenise -L ANSI-C +*/ + +#define TOTAL_KEYWORDS 23 +#define MIN_WORD_LENGTH 2 +#define MAX_WORD_LENGTH 14 +#define MIN_HASH_VALUE 2 +#define MAX_HASH_VALUE 38 +/* maximum key range = 37, duplicates = 0 */ + +#ifdef __GNUC__ +__inline +#endif +static unsigned int +imap_hash (register const char *str, register unsigned int len) +{ + static unsigned char asso_values[] = + { + 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, + 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, + 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, + 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, + 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, + 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, + 39, 39, 39, 39, 39, 10, 15, 39, 20, 0, + 0, 39, 0, 10, 39, 0, 39, 39, 10, 0, + 0, 39, 0, 10, 5, 10, 39, 39, 39, 0, + 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, + 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, + 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, + 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, + 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, + 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, + 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, + 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, + 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, + 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, + 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, + 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, + 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, + 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, + 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, + 39, 39, 39, 39, 39, 39, 39, 39, 39, 39, + 39, 39, 39, 39, 39, 39 + }; + return len + asso_values[(unsigned char)str[len - 1]] + asso_values[(unsigned char)str[0]]; +} + +#ifdef __GNUC__ +__inline +#endif +enum _imap_id +imap_tokenise (register const char *str, register unsigned int len) +{ + static struct _imap_keyword wordlist[] = + { + {""}, {""}, + {"OK", IMAP_OK}, + {""}, {""}, + {"PARSE", IMAP_PARSE}, + {""}, + {"PREAUTH", IMAP_PREAUTH}, + {"ENVELOPE", IMAP_ENVELOPE}, + {"READ-ONLY", IMAP_READ_ONLY}, + {"READ-WRITE", IMAP_READ_WRITE}, + {"RFC822.SIZE", IMAP_RFC822_SIZE}, + {"NO", IMAP_NO}, + {"RFC822.HEADER", IMAP_RFC822_HEADER}, + {"TRYCREATE", IMAP_TRYCREATE}, + {"FLAGS", IMAP_FLAGS}, + {"RFC822.TEXT", IMAP_RFC822_TEXT}, + {"NEWNAME", IMAP_NEWNAME}, + {"BYE", IMAP_BYE}, + {"BODY", IMAP_BODY}, + {"ALERT", IMAP_ALERT}, + {"UIDVALIDITY", IMAP_UIDVALIDITY}, + {"INTERNALDATE", IMAP_INTERNALDATE}, + {""}, + {"PERMANENTFLAGS", IMAP_PERMANENTFLAGS}, + {""}, + {"UNSEEN", IMAP_UNSEEN}, + {""}, + {"BODYSTRUCTURE", IMAP_BODYSTRUCTURE}, + {""}, {""}, {""}, {""}, + {"UID", IMAP_UID}, + {""}, {""}, {""}, {""}, + {"BAD", IMAP_BAD} + }; + + if (len <= MAX_WORD_LENGTH && len >= MIN_WORD_LENGTH) + { + register int key = imap_hash (str, len); + + if (key <= MAX_HASH_VALUE && key >= 0) + { + register const char *s = wordlist[key].name; + + if (*str == *s && !strcmp (str + 1, s + 1)) + return wordlist[key].id; + } + } + return 0; +} + +/* flag table */ +static struct { + char *name; + guint32 flag; +} flag_table[] = { + { "\\ANSWERED", CAMEL_MESSAGE_ANSWERED }, + { "\\DELETED", CAMEL_MESSAGE_DELETED }, + { "\\DRAFT", CAMEL_MESSAGE_DRAFT }, + { "\\FLAGGED", CAMEL_MESSAGE_FLAGGED }, + { "\\SEEN", CAMEL_MESSAGE_SEEN }, + { "\\RECENT", CAMEL_IMAPP_MESSAGE_RECENT }, + { "\\*", CAMEL_MESSAGE_USER }, +}; + +/* utility functions + shoudl this be part of imapp-driver? */ +/* mabye this should be a stream op? */ +void +imap_parse_flags(CamelIMAPPStream *stream, guint32 *flagsp) +/* throws IO,PARSE exception */ +{ + int tok, len, i; + unsigned char *token, *p, c; + guint32 flags = 0; + + *flagsp = flags; + + tok = camel_imapp_stream_token(stream, &token, &len); + if (tok == '(') { + do { + tok = camel_imapp_stream_token(stream, &token, &len); + if (tok == IMAP_TOK_TOKEN) { + p = token; + while ((c=*p)) + *p++ = toupper(c); + for (i=0;i<(int)(sizeof(flag_table)/sizeof(flag_table[0]));i++) + if (!strcmp(token, flag_table[i].name)) + flags |= flag_table[i].flag; + } else if (tok != ')') { + camel_exception_throw(1, "expecting flag"); + } + } while (tok != ')'); + } else { + camel_exception_throw(1, "expecting flag list"); + } + + *flagsp = flags; +} + +void +imap_write_flags(CamelStream *stream, guint32 flags) +/* throws IO exception */ +{ + int i; + + /* all this ugly exception throwing goes away once camel streams throw their own? */ + if (camel_stream_write(stream, "(", 1) == -1) + camel_exception_throw(1, "io error: %s", strerror(errno)); + + for (i=0;flags!=0 && i<(int)(sizeof(flag_table)/sizeof(flag_table[0]));i++) { + if (flag_table[i].flag & flags) { + if (camel_stream_write(stream, flag_table[i].name, strlen(flag_table[i].name)) == -1) + camel_exception_throw(1, "io error: %s", strerror(errno)); + flags &= ~flag_table[i].flag; + if (flags != 0) + if (camel_stream_write(stream, " ", 1) == -1) + camel_exception_throw(1, "io error: %s", strerror(errno)); + } + } + + if (camel_stream_write(stream, ")", 1) == -1) + camel_exception_throw(1, "io error: %s", strerror(errno)); +} + +/* +body ::= "(" body_type_1part / body_type_mpart ")" + +body_extension ::= nstring / number / "(" 1#body_extension ")" + ;; Future expansion. Client implementations + ;; MUST accept body_extension fields. Server + ;; implementations MUST NOT generate + ;; body_extension fields except as defined by + ;; future standard or standards-track + ;; revisions of this specification. + +body_ext_1part ::= body_fld_md5 [SPACE body_fld_dsp + [SPACE body_fld_lang + [SPACE 1#body_extension]]] + ;; MUST NOT be returned on non-extensible + ;; "BODY" fetch + +body_ext_mpart ::= body_fld_param + [SPACE body_fld_dsp SPACE body_fld_lang + [SPACE 1#body_extension]] + ;; MUST NOT be returned on non-extensible + ;; "BODY" fetch + +body_fields ::= body_fld_param SPACE body_fld_id SPACE + body_fld_desc SPACE body_fld_enc SPACE + body_fld_octets + +body_fld_desc ::= nstring + +body_fld_dsp ::= "(" string SPACE body_fld_param ")" / nil + +body_fld_enc ::= (<"> ("7BIT" / "8BIT" / "BINARY" / "BASE64"/ + "QUOTED-PRINTABLE") <">) / string + +body_fld_id ::= nstring + +body_fld_lang ::= nstring / "(" 1#string ")" + +body_fld_lines ::= number + +body_fld_md5 ::= nstring + +body_fld_octets ::= number + +body_fld_param ::= "(" 1#(string SPACE string) ")" / nil + +body_type_1part ::= (body_type_basic / body_type_msg / body_type_text) + [SPACE body_ext_1part] + +body_type_basic ::= media_basic SPACE body_fields + ;; MESSAGE subtype MUST NOT be "RFC822" + +body_type_mpart ::= 1*body SPACE media_subtype + [SPACE body_ext_mpart] + +body_type_msg ::= media_message SPACE body_fields SPACE envelope + SPACE body SPACE body_fld_lines + +body_type_text ::= media_text SPACE body_fields SPACE body_fld_lines + +envelope ::= "(" env_date SPACE env_subject SPACE env_from + SPACE env_sender SPACE env_reply_to SPACE env_to + SPACE env_cc SPACE env_bcc SPACE env_in_reply_to + SPACE env_message_id ")" + +env_bcc ::= "(" 1*address ")" / nil + +env_cc ::= "(" 1*address ")" / nil + +env_date ::= nstring + +env_from ::= "(" 1*address ")" / nil + +env_in_reply_to ::= nstring + +env_message_id ::= nstring + +env_reply_to ::= "(" 1*address ")" / nil + +env_sender ::= "(" 1*address ")" / nil + +env_subject ::= nstring + +env_to ::= "(" 1*address ")" / nil + +media_basic ::= (<"> ("APPLICATION" / "AUDIO" / "IMAGE" / + "MESSAGE" / "VIDEO") <">) / string) + SPACE media_subtype + ;; Defined in [MIME-IMT] + +media_message ::= <"> "MESSAGE" <"> SPACE <"> "RFC822" <"> + ;; Defined in [MIME-IMT] + +media_subtype ::= string + ;; Defined in [MIME-IMT] + +media_text ::= <"> "TEXT" <"> SPACE media_subtype + ;; Defined in [MIME-IMT] + + + + ( "type" "subtype" body_fields [envelope body body_fld_lines] + [body_fld_lines] + + + + (("TEXT" "PLAIN" ("CHARSET" + "US-ASCII") NIL NIL "7BIT" 1152 23)("TEXT" "PLAIN" + ("CHARSET" "US-ASCII" "NAME" "cc.diff") + "<960723163407.20117h@cac.washington.edu>" + "Compiler diff" "BASE64" 4554 73) "MIXED")) + +*/ + +/* +struct _body_fields { + struct _header_content_type *ct; + char *msgid, *desc; + CamelMimePartEncodingType encoding; + guint32 size; + };*/ + +void +imap_free_body(struct _CamelMessageContentInfo *cinfo) +{ + struct _CamelMessageContentInfo *list, *next; + + list = cinfo->childs; + while (list) { + next = list->next; + imap_free_body(list); + list = next; + } + + if (cinfo->type) + header_content_type_unref(cinfo->type); + g_free(cinfo->id); + g_free(cinfo->description); + g_free(cinfo->encoding); + g_free(cinfo); +} + +void +imap_parse_param_list(CamelIMAPPStream *is, struct _header_param **plist) +{ + int tok, len; + unsigned char *token, *param; + + p(printf("body_fld_param\n")); + + /* body_fld_param ::= "(" 1#(string SPACE string) ")" / nil */ + tok = camel_imapp_stream_token(is, &token, &len); + if (tok == '(') { + while (1) { + tok = camel_imapp_stream_token(is, &token, &len); + if (tok == ')') + break; + camel_imapp_stream_ungettoken(is, tok, token, len); + + camel_imapp_stream_astring(is, &token); + param = alloca(strlen(token)+1); + strcpy(param, token); + camel_imapp_stream_astring(is, &token); + header_set_param(plist, param, token); + } + } /* else check nil? no need */ +} + +struct _CamelMimeDisposition * +imap_parse_ext_optional(CamelIMAPPStream *is) +{ + int tok, len; + unsigned char *token; + struct _CamelMimeDisposition * volatile dinfo = NULL; + + /* this parses both extension types, from the body_fld_dsp onwards */ + /* although the grammars are different, they can be parsed the same way */ + + /* body_ext_1part ::= body_fld_md5 [SPACE body_fld_dsp + [SPACE body_fld_lang + [SPACE 1#body_extension]]] + ;; MUST NOT be returned on non-extensible + ;; "BODY" fetch */ + + /* body_ext_mpart ::= body_fld_param + [SPACE body_fld_dsp SPACE body_fld_lang + [SPACE 1#body_extension]] + ;; MUST NOT be returned on non-extensible + ;; "BODY" fetch */ + + CAMEL_TRY { + /* body_fld_dsp ::= "(" string SPACE body_fld_param ")" / nil */ + + tok = camel_imapp_stream_token(is, &token, &len); + switch (tok) { + case '(': + dinfo = g_malloc0(sizeof(*dinfo)); + dinfo->refcount = 1; + /* should be string */ + camel_imapp_stream_astring(is, &token); + + dinfo->disposition = g_strdup(token); + imap_parse_param_list(is, &dinfo->params); + case IMAP_TOK_TOKEN: + d(printf("body_fld_dsp: NIL\n")); + break; + default: + camel_exception_throw(1, "body_fld_disp: expecting nil or list"); + } + + p(printf("body_fld_lang\n")); + + /* body_fld_lang ::= nstring / "(" 1#string ")" */ + + /* we just drop the lang string/list, save it somewhere? */ + + tok = camel_imapp_stream_token(is, &token, &len); + switch (tok) { + case '(': + while (1) { + tok = camel_imapp_stream_token(is, &token, &len); + if (tok == ')') { + break; + } else if (tok != IMAP_TOK_STRING) { + camel_exception_throw(1, "expecting string"); + } + } + break; + case IMAP_TOK_TOKEN: + d(printf("body_fld_lang = nil\n")); + /* treat as 'nil' */ + break; + case IMAP_TOK_STRING: + /* we have a string */ + break; + case IMAP_TOK_LITERAL: + /* we have a literal string */ + camel_imapp_stream_set_literal(is, len); + while ((tok = camel_imapp_stream_getl(is, &token, &len)) > 0) { + d(printf("Skip literal data '%.*s'\n", (int)len, token)); + } + break; + + } + } CAMEL_CATCH(ex) { + if (dinfo) + header_disposition_unref(dinfo); + camel_exception_throw_ex(ex); + } CAMEL_DONE; + + return dinfo; +} + +struct _CamelMessageContentInfo * +imap_parse_body_fields(CamelIMAPPStream *is) +{ + unsigned char *token, *type; + struct _CamelMessageContentInfo *cinfo; + + /* body_fields ::= body_fld_param SPACE body_fld_id SPACE + body_fld_desc SPACE body_fld_enc SPACE + body_fld_octets */ + + p(printf("body_fields\n")); + + cinfo = g_malloc0(sizeof(*cinfo)); + + CAMEL_TRY { + /* this should be string not astring */ + camel_imapp_stream_astring(is, &token); + type = alloca(strlen(token)+1); + strcpy(type, token); + camel_imapp_stream_astring(is, &token); + cinfo->type = header_content_type_new(type, token); + imap_parse_param_list(is, &cinfo->type->params); + + /* body_fld_id ::= nstring */ + camel_imapp_stream_nstring(is, &token); + cinfo->id = g_strdup(token); + + /* body_fld_desc ::= nstring */ + camel_imapp_stream_nstring(is, &token); + cinfo->description = g_strdup(token); + + /* body_fld_enc ::= (<"> ("7BIT" / "8BIT" / "BINARY" / "BASE64"/ + "QUOTED-PRINTABLE") <">) / string */ + camel_imapp_stream_astring(is, &token); + cinfo->encoding = g_strdup(token); + + /* body_fld_octets ::= number */ + cinfo->size = camel_imapp_stream_number(is); + } CAMEL_CATCH(ex) { + imap_free_body(cinfo); + camel_exception_throw_ex(ex); + } CAMEL_DONE; + + return cinfo; +} + +struct _header_address * +imap_parse_address_list(CamelIMAPPStream *is) +/* throws PARSE,IO exception */ +{ + int tok, len; + unsigned char *token, *host, *mbox; + struct _header_address *list = NULL; + + /* "(" 1*address ")" / nil */ + + CAMEL_TRY { + tok = camel_imapp_stream_token(is, &token, &len); + if (tok == '(') { + while (1) { + struct _header_address *addr, *group = NULL; + + /* address ::= "(" addr_name SPACE addr_adl SPACE addr_mailbox + SPACE addr_host ")" */ + tok = camel_imapp_stream_token(is, &token, &len); + if (tok == ')') + break; + if (tok != '(') + camel_exception_throw(1, "missing '(' for address"); + + addr = header_address_new(); + addr->type = HEADER_ADDRESS_NAME; + tok = camel_imapp_stream_nstring(is, &token); + addr->name = g_strdup(token); + /* we ignore the route, nobody uses it in the real world */ + tok = camel_imapp_stream_nstring(is, &token); + + /* [RFC-822] group syntax is indicated by a special + form of address structure in which the host name + field is NIL. If the mailbox name field is also + NIL, this is an end of group marker (semi-colon in + RFC 822 syntax). If the mailbox name field is + non-NIL, this is a start of group marker, and the + mailbox name field holds the group name phrase. */ + + tok = camel_imapp_stream_nstring(is, &mbox); + mbox = g_strdup(mbox); + tok = camel_imapp_stream_nstring(is, &host); + if (host == NULL) { + if (mbox == NULL) { + group = NULL; + } else { + d(printf("adding group '%s'\n", mbox)); + g_free(addr->name); + addr->name = mbox; + addr->type = HEADER_ADDRESS_GROUP; + header_address_list_append(&list, addr); + group = addr; + } + } else { + addr->v.addr = g_strdup_printf("%s%s%s", mbox?(char *)mbox:"", host?"@":"", host?(char *)host:""); + g_free(mbox); + d(printf("adding address '%s'\n", addr->v.addr)); + if (group != NULL) + header_address_add_member(group, addr); + else + header_address_list_append(&list, addr); + } + do { + tok = camel_imapp_stream_token(is, &token, &len); + } while (tok != ')'); + } + } else { + d(printf("empty, nil '%s'\n", token)); + } + } CAMEL_CATCH(ex) { + header_address_list_clear(&list); + camel_exception_throw_ex(ex); + } CAMEL_DONE; + + return list; +} + +struct _CamelMessageInfo * +imap_parse_envelope(CamelIMAPPStream *is) +{ + int tok, len; + unsigned char *token; + struct _header_address *addr, *addr_from; + char *addrstr; + struct _CamelMessageInfo *minfo; + + /* envelope ::= "(" env_date SPACE env_subject SPACE env_from + SPACE env_sender SPACE env_reply_to SPACE env_to + SPACE env_cc SPACE env_bcc SPACE env_in_reply_to + SPACE env_message_id ")" */ + + p(printf("envelope\n")); + + minfo = camel_message_info_new(); + + CAMEL_TRY { + tok = camel_imapp_stream_token(is, &token, &len); + if (tok != '(') + camel_exception_throw(1, "envelope: expecting '('"); + + /* env_date ::= nstring */ + camel_imapp_stream_nstring(is, &token); + minfo->date_sent = header_decode_date(token, NULL); + + /* env_subject ::= nstring */ + tok = camel_imapp_stream_nstring(is, &token); + /* DUH: this free's it!: camel_message_info_set_subject(minfo, token); */ + e_poolv_set(minfo->strings, CAMEL_MESSAGE_INFO_SUBJECT, token, FALSE); + + /* we merge from/sender into from, append should probably merge more smartly? */ + + /* env_from ::= "(" 1*address ")" / nil */ + addr_from = imap_parse_address_list(is); + + /* env_sender ::= "(" 1*address ")" / nil */ + addr = imap_parse_address_list(is); + if (addr_from) { + header_address_list_clear(&addr); +#if 0 + if (addr) + header_address_list_append_list(&addr_from, &addr); +#endif + } else { + if (addr) + addr_from = addr; + } + + if (addr_from) { + addrstr = header_address_list_format(addr_from); + camel_message_info_set_from(minfo, addrstr); + header_address_list_clear(&addr_from); + } + + /* we dont keep reply_to */ + + /* env_reply_to ::= "(" 1*address ")" / nil */ + addr = imap_parse_address_list(is); + header_address_list_clear(&addr); + + /* env_to ::= "(" 1*address ")" / nil */ + addr = imap_parse_address_list(is); + if (addr) { + addrstr = header_address_list_format(addr); + camel_message_info_set_to(minfo, addrstr); + header_address_list_clear(&addr); + } + + /* env_cc ::= "(" 1*address ")" / nil */ + addr = imap_parse_address_list(is); + if (addr) { + addrstr = header_address_list_format(addr); + camel_message_info_set_cc(minfo, addrstr); + header_address_list_clear(&addr); + } + + /* we dont keep bcc either */ + + /* env_bcc ::= "(" 1*address ")" / nil */ + addr = imap_parse_address_list(is); + header_address_list_clear(&addr); + + /* FIXME: need to put in-reply-to into references hash list */ + + /* env_in_reply_to ::= nstring */ + tok = camel_imapp_stream_nstring(is, &token); + + /* FIXME: need to put message-id into message-id hash */ + + /* env_message_id ::= nstring */ + tok = camel_imapp_stream_nstring(is, &token); + + tok = camel_imapp_stream_token(is, &token, &len); + if (tok != ')') + camel_exception_throw(1, "expecting ')'"); + } CAMEL_CATCH(ex) { + camel_message_info_free(minfo); + camel_exception_throw_ex(ex); + } CAMEL_DONE; + + return minfo; +} + +struct _CamelMessageContentInfo * +imap_parse_body(CamelIMAPPStream *is) +{ + int tok, len; + unsigned char *token; + struct _CamelMessageContentInfo * volatile cinfo = NULL; + struct _CamelMessageContentInfo *subinfo, *last; + struct _CamelMimeDisposition * volatile dinfo = NULL; + struct _CamelMessageInfo * volatile minfo = NULL; + + /* body ::= "(" body_type_1part / body_type_mpart ")" */ + + p(printf("body\n")); + + CAMEL_TRY { + tok = camel_imapp_stream_token(is, &token, &len); + if (tok != '(') + camel_exception_throw(1, "body: expecting '('"); + + /* 1*body (optional for multiparts) */ + tok = camel_imapp_stream_token(is, &token, &len); + camel_imapp_stream_ungettoken(is, tok, token, len); + if (tok == '(') { + /* body_type_mpart ::= 1*body SPACE media_subtype + [SPACE body_ext_mpart] */ + + cinfo = g_malloc0(sizeof(*cinfo)); + last = (struct _CamelMessageContentInfo *)&cinfo->childs; + do { + subinfo = imap_parse_body(is); + last->next = subinfo; + last = subinfo; + subinfo->parent = cinfo; + tok = camel_imapp_stream_token(is, &token, &len); + camel_imapp_stream_ungettoken(is, tok, token, len); + } while (tok == '('); + + d(printf("media_subtype\n")); + + camel_imapp_stream_astring(is, &token); + cinfo->type = header_content_type_new("multipart", token); + + /* body_ext_mpart ::= body_fld_param + [SPACE body_fld_dsp SPACE body_fld_lang + [SPACE 1#body_extension]] + ;; MUST NOT be returned on non-extensible + ;; "BODY" fetch */ + + d(printf("body_ext_mpart\n")); + + tok = camel_imapp_stream_token(is, &token, &len); + camel_imapp_stream_ungettoken(is, tok, token, len); + if (tok == '(') { + imap_parse_param_list(is, &cinfo->type->params); + + /* body_fld_dsp ::= "(" string SPACE body_fld_param ")" / nil */ + + tok = camel_imapp_stream_token(is, &token, &len); + camel_imapp_stream_ungettoken(is, tok, token, len); + if (tok == '(' || tok == IMAP_TOK_TOKEN) { + dinfo = imap_parse_ext_optional(is); + /* other extension fields?, soaked up below */ + } else { + camel_imapp_stream_ungettoken(is, tok, token, len); + } + } + } else { + /* body_type_1part ::= (body_type_basic / body_type_msg / body_type_text) + [SPACE body_ext_1part] + + body_type_basic ::= media_basic SPACE body_fields + body_type_text ::= media_text SPACE body_fields SPACE body_fld_lines + body_type_msg ::= media_message SPACE body_fields SPACE envelope + SPACE body SPACE body_fld_lines */ + + d(printf("Single part body\n")); + + cinfo = imap_parse_body_fields(is); + + d(printf("envelope?\n")); + + /* do we have an envelope following */ + tok = camel_imapp_stream_token(is, &token, &len); + camel_imapp_stream_ungettoken(is, tok, token, len); + if (tok == '(') { + /* what do we do with the envelope?? */ + minfo = imap_parse_envelope(is); + /* what do we do with the message content info?? */ + minfo->content = imap_parse_body(is); + camel_message_info_free(minfo); + minfo = NULL; + d(printf("Scanned envelope - what do i do with it?\n")); + } + + d(printf("fld_lines?\n")); + + /* do we have fld_lines following? */ + tok = camel_imapp_stream_token(is, &token, &len); + if (tok == IMAP_TOK_INT) { + d(printf("field lines: %s\n", token)); + tok = camel_imapp_stream_token(is, &token, &len); + } + camel_imapp_stream_ungettoken(is, tok, token, len); + + /* body_ext_1part ::= body_fld_md5 [SPACE body_fld_dsp + [SPACE body_fld_lang + [SPACE 1#body_extension]]] + ;; MUST NOT be returned on non-extensible + ;; "BODY" fetch */ + + d(printf("extension data?\n")); + + if (tok != ')') { + camel_imapp_stream_nstring(is, &token); + + d(printf("md5: %s\n", token?(char *)token:"NIL")); + + /* body_fld_dsp ::= "(" string SPACE body_fld_param ")" / nil */ + + tok = camel_imapp_stream_token(is, &token, &len); + camel_imapp_stream_ungettoken(is, tok, token, len); + if (tok == '(' || tok == IMAP_TOK_TOKEN) { + dinfo = imap_parse_ext_optional(is); + /* then other extension fields, soaked up below */ + } + } + } + + /* soak up any other extension fields that may be present */ + /* there should only be simple tokens, no lists */ + do { + tok = camel_imapp_stream_token(is, &token, &len); + if (tok != ')') + d(printf("Dropping extension data '%s'\n", token)); + } while (tok != ')'); + } CAMEL_CATCH(ex) { + if (cinfo) + imap_free_body(cinfo); + if (dinfo) + header_disposition_unref(dinfo); + if (minfo) + camel_message_info_free(minfo); + camel_exception_throw_ex(ex); + } CAMEL_DONE; + + /* FIXME: do something with the disposition, currently we have no way to pass it out? */ + if (dinfo) + header_disposition_unref(dinfo); + + return cinfo; +} + +char * +imap_parse_section(CamelIMAPPStream *is) +{ + int tok, len; + unsigned char *token; + char * volatile section = NULL; + + /* currently we only return the part within the [section] specifier + any header fields are parsed, but dropped */ + + /* + section ::= "[" [section_text / + (nz_number *["." nz_number] ["." (section_text / "MIME")])] "]" + + section_text ::= "HEADER" / "HEADER.FIELDS" [".NOT"] + SPACE header_list / "TEXT" + */ + + CAMEL_TRY { + tok = camel_imapp_stream_token(is, &token, &len); + if (tok != '[') + camel_exception_throw(1, "section: expecting '['"); + + tok = camel_imapp_stream_token(is, &token, &len); + if (tok == IMAP_TOK_INT || tok == IMAP_TOK_TOKEN) + section = g_strdup(token); + else if (tok == ']') { + section = g_strdup(""); + camel_imapp_stream_ungettoken(is, tok, token, len); + } else + camel_exception_throw(1, "section: expecting token"); + + /* header_list ::= "(" 1#header_fld_name ")" + header_fld_name ::= astring */ + + /* we dont need the header specifiers */ + tok = camel_imapp_stream_token(is, &token, &len); + if (tok == '(') { + do { + tok = camel_imapp_stream_token(is, &token, &len); + if (tok == IMAP_TOK_STRING || tok == IMAP_TOK_TOKEN || tok == IMAP_TOK_INT) { + /* ?do something? */ + } else if (tok != ')') + camel_exception_throw(1, "section: header fields: expecting string"); + } while (tok != ')'); + tok = camel_imapp_stream_token(is, &token, &len); + } + + if (tok != ']') + camel_exception_throw(1, "section: expecting ']'"); + } CAMEL_CATCH(ex) { + g_free(section); + camel_exception_throw_ex(ex); + } CAMEL_DONE; + + return section; +} + +void +imap_free_fetch(struct _fetch_info *finfo) +{ + if (finfo == NULL) + return; + + if (finfo->body) + camel_object_unref((CamelObject *)finfo->body); + if (finfo->text) + camel_object_unref((CamelObject *)finfo->text); + if (finfo->header) + camel_object_unref((CamelObject *)finfo->header); + if (finfo->minfo) + camel_message_info_free(finfo->minfo); + if (finfo->cinfo) + imap_free_body(finfo->cinfo); + g_free(finfo->date); + g_free(finfo->section); + g_free(finfo->uid); + g_free(finfo); +} + +extern void camel_content_info_dump(CamelMessageContentInfo *ci, int depth); +extern void camel_message_info_dump(CamelMessageInfo *mi); + +#include <camel/camel-stream-fs.h> + +/* debug, dump one out */ +void +imap_dump_fetch(struct _fetch_info *finfo) +{ + CamelStream *sout; + int fd; + + printf("Fetch info:\n"); + if (finfo == NULL) { + printf("Empty\n"); + return; + } + + fd = dup(1); + sout = camel_stream_fs_new_with_fd(fd); + if (finfo->body) { + camel_stream_printf(sout, "Body content:\n"); + camel_stream_write_to_stream(finfo->body, sout); + } + if (finfo->text) { + camel_stream_printf(sout, "Text content:\n"); + camel_stream_write_to_stream(finfo->text, sout); + } + if (finfo->header) { + camel_stream_printf(sout, "Header content:\n"); + camel_stream_write_to_stream(finfo->header, sout); + } + if (finfo->minfo) { + camel_stream_printf(sout, "Message Info:\n"); + camel_message_info_dump(finfo->minfo); + } + if (finfo->cinfo) { + camel_stream_printf(sout, "Content Info:\n"); + camel_content_info_dump(finfo->cinfo, 0); + } + if (finfo->got & FETCH_SIZE) + camel_stream_printf(sout, "Size: %d\n", (int)finfo->size); + if (finfo->got & FETCH_BODY) + camel_stream_printf(sout, "Offset: %d\n", (int)finfo->offset); + if (finfo->got & FETCH_FLAGS) + camel_stream_printf(sout, "Flags: %08x\n", (int)finfo->flags); + if (finfo->date) + camel_stream_printf(sout, "Date: '%s'\n", finfo->date); + if (finfo->section) + camel_stream_printf(sout, "Section: '%s'\n", finfo->section); + if (finfo->date) + camel_stream_printf(sout, "UID: '%s'\n", finfo->uid); + camel_object_unref((CamelObject *)sout); +} + +struct _fetch_info * +imap_parse_fetch(CamelIMAPPStream *is) +{ + int tok, len; + unsigned char *token, *p, c; + struct _fetch_info *finfo; + + finfo = g_malloc0(sizeof(*finfo)); + + CAMEL_TRY { + tok = camel_imapp_stream_token(is, &token, &len); + if (tok != '(') + camel_exception_throw(1, "fetch: expecting '('"); + + while ( (tok = camel_imapp_stream_token(is, &token, &len)) == IMAP_TOK_TOKEN ) { + + p = token; + while ((c=*p)) + *p++ = toupper(c); + + switch(imap_tokenise(token, len)) { + case IMAP_ENVELOPE: + finfo->minfo = imap_parse_envelope(is); + finfo->got |= FETCH_MINFO; + break; + case IMAP_FLAGS: + imap_parse_flags(is, &finfo->flags); + finfo->got |= FETCH_FLAGS; + break; + case IMAP_INTERNALDATE: + camel_imapp_stream_nstring(is, &token); + /* TODO: convert to camel format? */ + finfo->date = g_strdup(token); + finfo->got |= FETCH_DATE; + break; + case IMAP_RFC822_HEADER: + camel_imapp_stream_nstring_stream(is, &finfo->header); + finfo->got |= FETCH_HEADER; + break; + case IMAP_RFC822_TEXT: + camel_imapp_stream_nstring_stream(is, &finfo->text); + finfo->got |= FETCH_TEXT; + break; + case IMAP_RFC822_SIZE: + finfo->size = camel_imapp_stream_number(is); + finfo->got |= FETCH_SIZE; + break; + case IMAP_BODYSTRUCTURE: + finfo->cinfo = imap_parse_body(is); + finfo->got |= FETCH_CINFO; + break; + case IMAP_BODY: + tok = camel_imapp_stream_token(is, &token, &len); + camel_imapp_stream_ungettoken(is, tok, token, len); + if (tok == '(') { + finfo->cinfo = imap_parse_body(is); + finfo->got |= FETCH_CINFO; + } else if (tok == '[') { + finfo->section = imap_parse_section(is); + finfo->got |= FETCH_SECTION; + tok = camel_imapp_stream_token(is, &token, &len); + if (token[0] == '<') { + finfo->offset = strtoul(token+1, NULL, 10); + } else { + camel_imapp_stream_ungettoken(is, tok, token, len); + } + camel_imapp_stream_nstring_stream(is, &finfo->body); + finfo->got |= FETCH_BODY; + } else { + camel_exception_throw(1, "unknown body response"); + } + break; + case IMAP_UID: + tok = camel_imapp_stream_token(is, &token, &len); + if (tok != IMAP_TOK_INT) + camel_exception_throw(1, "uid not integer"); + finfo->uid = g_strdup(token); + finfo->got |= FETCH_UID; + break; + default: + camel_exception_throw(1, "unknown body response"); + } + } + + if (tok != ')') + camel_exception_throw(1, "missing closing ')' on fetch response"); + } CAMEL_CATCH(ex) { + imap_free_fetch(finfo); + camel_exception_throw_ex(ex); + } CAMEL_DONE; + + return finfo; +} + +/* rfc 2060 section 7.1 Status Responses */ +/* shoudl this start after [ or before the [? token_unget anyone? */ +struct _status_info * +imap_parse_status(CamelIMAPPStream *is) +{ + int tok, len; + unsigned char *token; + struct _status_info *sinfo; + + sinfo = g_malloc0(sizeof(*sinfo)); + + CAMEL_TRY { + camel_imapp_stream_atom(is, &token, &len); + + /* + resp_cond_auth ::= ("OK" / "PREAUTH") SPACE resp_text + ;; Authentication condition + + resp_cond_bye ::= "BYE" SPACE resp_text + + resp_cond_state ::= ("OK" / "NO" / "BAD") SPACE resp_text + ;; Status condition + */ + + sinfo->result = imap_tokenise(token, len); + switch (sinfo->result) { + case IMAP_OK: + case IMAP_NO: + case IMAP_BAD: + case IMAP_PREAUTH: + case IMAP_BYE: + break; + default: + camel_exception_throw(1, "expecting OK/NO/BAD"); + } + + tok = camel_imapp_stream_token(is, &token, &len); + if (tok == '[') { + camel_imapp_stream_atom(is, &token, &len); + sinfo->condition = imap_tokenise(token, len); + + /* parse any details */ + switch (sinfo->condition) { + case IMAP_READ_ONLY: + case IMAP_READ_WRITE: + case IMAP_ALERT: + case IMAP_PARSE: + case IMAP_TRYCREATE: + break; + case IMAP_NEWNAME: + /* the rfc doesn't specify the bnf for this */ + camel_imapp_stream_astring(is, &token); + sinfo->u.newname.oldname = g_strdup(token); + camel_imapp_stream_astring(is, &token); + sinfo->u.newname.newname = g_strdup(token); + break; + case IMAP_PERMANENTFLAGS: + imap_parse_flags(is, &sinfo->u.permanentflags); + break; + case IMAP_UIDVALIDITY: + sinfo->u.uidvalidity = camel_imapp_stream_number(is); + break; + case IMAP_UNSEEN: + sinfo->u.unseen = camel_imapp_stream_number(is); + break; + default: + sinfo->condition = IMAP_UNKNOWN; + printf("Got unknown response code: %s: ignored\n", token); + } + + /* ignore anything we dont know about */ + do { + tok = camel_imapp_stream_token(is, &token, &len); + if (tok == '\n') + camel_exception_throw(1, "server response truncated"); + } while (tok != ']'); + } else { + camel_imapp_stream_ungettoken(is, tok, token, len); + } + + /* and take the human readable response */ + camel_imapp_stream_text(is, (unsigned char **)&sinfo->text); + } CAMEL_CATCH(ex) { + imap_free_status(sinfo); + camel_exception_throw_ex(ex); + } CAMEL_DONE; + + return sinfo; +} + +void +imap_free_status(struct _status_info *sinfo) +{ + if (sinfo == NULL) + return; + + switch (sinfo->condition) { + case IMAP_NEWNAME: + g_free(sinfo->u.newname.oldname); + g_free(sinfo->u.newname.newname); + default: + break; + } + + g_free(sinfo->text); + g_free(sinfo); +} + +/* FIXME: use tokeniser? */ +/* FIXME: real flags */ +static struct { + char *name; + guint32 flag; +} list_flag_table[] = { + { "\\NOINFERIORS", CAMEL_FOLDER_NOINFERIORS }, + { "\\NOSELECT", CAMEL_FOLDER_NOSELECT }, + { "\\MARKED", 1<<8 }, + { "\\UNMARKED", 1<<9 }, +}; + +struct _list_info * +imap_parse_list(CamelIMAPPStream *is) +/* throws io, parse */ +{ + int tok, len, i; + unsigned char *token, *p, c; + struct _list_info * volatile linfo; + + linfo = g_malloc0(sizeof(*linfo)); + + CAMEL_TRY { + /* mailbox_list ::= "(" #("\Marked" / "\Noinferiors" / + "\Noselect" / "\Unmarked" / flag_extension) ")" + SPACE (<"> QUOTED_CHAR <"> / nil) SPACE mailbox */ + + tok = camel_imapp_stream_token(is, &token, &len); + if (tok != '(') + camel_exception_throw(1, "list: expecting '('"); + + while ( (tok = camel_imapp_stream_token(is, &token, &len)) != ')' ) { + if (tok == IMAP_TOK_STRING || tok == IMAP_TOK_TOKEN) { + p = token; + while ((c=*p)) + *p++ = toupper(c); + for (i=0;i<(int)(sizeof(list_flag_table)/sizeof(list_flag_table[0]));i++) + if (!strcmp(token, list_flag_table[i].name)) + linfo->flags |= list_flag_table[i].flag; + } else { + camel_exception_throw(1, "list: expecting flag or ')'"); + } + } + + camel_imapp_stream_nstring(is, &token); + linfo->separator = token?*token:0; + camel_imapp_stream_astring(is, &token); + linfo->name = g_strdup(token); + } CAMEL_CATCH(ex) { + imap_free_list(linfo); + camel_exception_throw_ex(ex); + } CAMEL_DONE; + + return linfo; +} + +char * +imapp_list_get_path(struct _list_info *li) +{ + char *path, *p; + int c; + const char *f; + + if (li->separator != 0 && li->separator != '/') { + p = path = alloca(strlen(li->name)*3+1); + f = li->name; + while ( (c = *f++ & 0xff) ) { + if (c == li->separator) + *p++ = '/'; + else if (c == '/' || c == '%') + p += sprintf(p, "%%%02X", c); + else + *p++ = c; + } + *p = 0; + } else + path = li->name; + + return camel_utf7_utf8(path); +} + +void +imap_free_list(struct _list_info *linfo) +{ + if (linfo) { + g_free(linfo->name); + g_free(linfo); + } +} + + +/* ********************************************************************** */ +/* utility functions */ + +/* should the rest of imapp-utils go into imapp-parse? */ + +/* this creates a uid (or sequence number) set directly into the command, + optionally breaking it into smaller chunks */ + +void +imapp_uidset_init(struct _uidset_state *ss, CamelIMAPPEngine *ie) +{ + ss->ie = ie; + ss->len = 0; + ss->start = 0; + ss->last = 0; +} + +int +imapp_uidset_done(struct _uidset_state *ss, CamelIMAPPCommand *ic) +{ + int ret = 0; + + if (ss->last != 0 && ss->last != ss->start) { + camel_imapp_engine_command_add(ss->ie, ic, ":%d", ss->last); + printf(":%d", ss->last); + } + + ret = ss->last != 0; + + ss->start = 0; + ss->last = 0; + ss->len = 0; + + return ret; +} + +int +imapp_uidset_add(struct _uidset_state *ss, CamelIMAPPCommand *ic, const char *uid) +{ + guint32 uidn; + + uidn = strtoul(uid, NULL, 10); + if (uidn == 0) + return -1; + + if (ss->last == 0) { + camel_imapp_engine_command_add(ss->ie, ic, "%d", uidn); + printf("%d", uidn); + ss->len ++; + ss->start = uidn; + } else { + if (ss->last != uidn-1) { + if (ss->last == ss->start) { + camel_imapp_engine_command_add(ss->ie, ic, ",%d", uidn); + printf(",%d", uidn); + ss->len ++; + } else { + camel_imapp_engine_command_add(ss->ie, ic, ":%d,%d", ss->last, uidn); + printf(":%d,%d", ss->last, uidn); + ss->len+=2; + } + ss->start = uidn; + } + } + + ss->last = uidn; + + if (ss->len > 10) { + imapp_uidset_done(ss, ic); + return 1; + } + + return 0; +} diff --git a/camel/providers/imapp/camel-imapp-utils.h b/camel/providers/imapp/camel-imapp-utils.h new file mode 100644 index 0000000000..76ec178b63 --- /dev/null +++ b/camel/providers/imapp/camel-imapp-utils.h @@ -0,0 +1,145 @@ + +#ifndef _CAMEL_IMAPP_UTILS_H +#define _CAMEL_IMAPP_UTILS_H + +#include <camel/camel-mime-utils.h> + +/* FIXME: the enum should be split up into logical groups, so that testing + can be done more accurately? */ + +/* list of strings we know about that can be *quickly* tokenised */ +enum _imap_id { + IMAP_UNKNOWN = 0, + IMAP_ALERT, + IMAP_BYE, + IMAP_BAD, + IMAP_NO, + IMAP_OK, + IMAP_PREAUTH, + IMAP_NEWNAME, + IMAP_PARSE, + IMAP_PERMANENTFLAGS, + IMAP_READ_ONLY, + IMAP_READ_WRITE, + IMAP_TRYCREATE, + IMAP_UIDVALIDITY, + IMAP_UNSEEN, + IMAP_ENVELOPE, + IMAP_FLAGS, + IMAP_INTERNALDATE, + IMAP_RFC822_HEADER, + IMAP_RFC822_TEXT, + IMAP_RFC822_SIZE, + IMAP_BODYSTRUCTURE, + IMAP_BODY, + IMAP_UID, +}; + +/* str MUST be in upper case, tokenised using gperf function */ +enum _imap_id imap_tokenise(register const char *str, register unsigned int len); + +/* this flag should be part of imapfoldersummary */ +enum { + CAMEL_IMAPP_MESSAGE_RECENT = (1<<8), +}; + +/* ********************************************************************** */ +void imap_parse_flags(CamelIMAPPStream *stream, guint32 *flagsp) /* IO,PARSE */; +void imap_write_flags(CamelStream *stream, guint32 flags) /* IO */; + +/* ********************************************************************** */ +void imap_parse_param_list(CamelIMAPPStream *is, struct _header_param **plist) /* IO,PARSE */; +struct _CamelMimeDisposition *imap_parse_ext_optional(CamelIMAPPStream *is) /* IO,PARSE */; +struct _CamelMessageContentInfo *imap_parse_body_fields(CamelIMAPPStream *is) /* IO,PARSE */; +struct _header_address *imap_parse_address_list(CamelIMAPPStream *is) /* IO,PARSE */; +struct _CamelMessageInfo *imap_parse_envelope(CamelIMAPPStream *is) /* IO, PARSE */; +struct _CamelMessageContentInfo *imap_parse_body(CamelIMAPPStream *is) /* IO,PARSE */; +char *imap_parse_section(CamelIMAPPStream *is) /* IO,PARSE */; +void imap_free_body(struct _CamelMessageContentInfo *cinfo); + +/* ********************************************************************** */ +/* all the possible stuff we might get from a fetch request */ +/* this assumes the caller/server doesn't send any one of these types twice */ +struct _fetch_info { + guint32 got; /* what we got, see below */ + CamelStream *body; /* BODY[.*](<.*>)? */ + CamelStream *text; /* RFC822.TEXT */ + CamelStream *header; /* RFC822.HEADER */ + CamelMessageInfo *minfo; /* ENVELOPE */ + CamelMessageContentInfo *cinfo; /* BODYSTRUCTURE,BODY */ + guint32 size; /* RFC822.SIZE */ + guint32 offset; /* start offset of a BODY[]<offset.length> request */ + guint32 flags; /* FLAGS */ + char *date; /* INTERNALDATE */ + char *section; /* section for a BODY[section] request */ + char *uid; /* UID */ +}; + +#define FETCH_BODY (1<<0) +#define FETCH_TEXT (1<<1) +#define FETCH_HEADER (1<<2) +#define FETCH_MINFO (1<<3) +#define FETCH_CINFO (1<<4) +#define FETCH_SIZE (1<<5) +#define FETCH_OFFSET (1<<6) +#define FETCH_FLAGS (1<<7) +#define FETCH_DATE (1<<8) +#define FETCH_SECTION (1<<9) +#define FETCH_UID (1<<10) + +struct _fetch_info *imap_parse_fetch(CamelIMAPPStream *is); +void imap_free_fetch(struct _fetch_info *finfo); +void imap_dump_fetch(struct _fetch_info *finfo); + +/* ********************************************************************** */ + +struct _status_info { + enum _imap_id result; /* ok/no/bad/preauth only */ + enum _imap_id condition; /* read-only/read-write/alert/parse/trycreate/newname/permanentflags/uidvalidity/unseen */ + + union { + struct { + char *oldname; + char *newname; + } newname; + guint32 permanentflags; + guint32 uidvalidity; + guint32 unseen; + } u; + + char *text; +}; + +struct _status_info *imap_parse_status(CamelIMAPPStream *is); +void imap_free_status(struct _status_info *sinfo); + +/* ********************************************************************** */ + +/* should this just return a FolderInfo? + should this just return the name & flags & separator by reference? */ +struct _list_info { + guint32 flags:24; + char separator; + char *name; +}; + +struct _list_info *imap_parse_list(CamelIMAPPStream *is); +char *imapp_list_get_path(struct _list_info *li); +void imap_free_list(struct _list_info *linfo); + +/* ********************************************************************** */ + +struct _uidset_state { + struct _CamelIMAPPEngine *ie; + int len; + guint32 start; + guint32 last; +}; + +struct _CamelIMAPPEngine; +struct _CamelIMAPPCommand; +void imapp_uidset_init(struct _uidset_state *ss, struct _CamelIMAPPEngine *ie); +int imapp_uidset_done(struct _uidset_state *ss, struct _CamelIMAPPCommand *ic); +int imapp_uidset_add(struct _uidset_state *ss, struct _CamelIMAPPCommand *ic, const char *uid); + +#endif diff --git a/camel/providers/imapp/libcamelimapp.urls b/camel/providers/imapp/libcamelimapp.urls new file mode 100644 index 0000000000..aad1347834 --- /dev/null +++ b/camel/providers/imapp/libcamelimapp.urls @@ -0,0 +1 @@ +imapp |