diff options
Diffstat (limited to 'camel/providers/imapp/camel-imapp-store.c')
-rw-r--r-- | camel/providers/imapp/camel-imapp-store.c | 1221 |
1 files changed, 1221 insertions, 0 deletions
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 |