/* -*- 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