camel-imap4 r2 - in trunk: . camel imap4



Author: fejj
Date: Sat May 10 17:53:05 2008
New Revision: 2
URL: http://svn.gnome.org/viewvc/camel-imap4?rev=2&view=rev

Log:
initial import of camel-imap4

Added:
   trunk/AUTHORS
   trunk/ChangeLog
   trunk/MAINTAINERS
   trunk/Makefile.am
   trunk/NEWS
   trunk/README
   trunk/acinclude.m4
   trunk/autogen.sh   (contents, props changed)
   trunk/camel/
   trunk/camel/camel-private.h
   trunk/configure.ac
   trunk/imap4/
   trunk/imap4/ChangeLog
   trunk/imap4/Makefile.am
   trunk/imap4/camel-imap4-command.c
   trunk/imap4/camel-imap4-command.h
   trunk/imap4/camel-imap4-engine.c
   trunk/imap4/camel-imap4-engine.h
   trunk/imap4/camel-imap4-folder.c
   trunk/imap4/camel-imap4-folder.h
   trunk/imap4/camel-imap4-journal.c
   trunk/imap4/camel-imap4-journal.h
   trunk/imap4/camel-imap4-provider.c
   trunk/imap4/camel-imap4-search.c
   trunk/imap4/camel-imap4-search.h
   trunk/imap4/camel-imap4-specials.c
   trunk/imap4/camel-imap4-specials.h
   trunk/imap4/camel-imap4-store-summary.c
   trunk/imap4/camel-imap4-store-summary.h
   trunk/imap4/camel-imap4-store.c
   trunk/imap4/camel-imap4-store.h
   trunk/imap4/camel-imap4-stream.c
   trunk/imap4/camel-imap4-stream.h
   trunk/imap4/camel-imap4-summary.c
   trunk/imap4/camel-imap4-summary.h
   trunk/imap4/camel-imap4-utils.c
   trunk/imap4/camel-imap4-utils.h
   trunk/imap4/libcamelimap4.urls

Added: trunk/AUTHORS
==============================================================================
--- (empty file)
+++ trunk/AUTHORS	Sat May 10 17:53:05 2008
@@ -0,0 +1 @@
+Jeffrey Stedfast <fejj gnome org>

Added: trunk/MAINTAINERS
==============================================================================
--- (empty file)
+++ trunk/MAINTAINERS	Sat May 10 17:53:05 2008
@@ -0,0 +1,3 @@
+Jeffrey Stedfast
+E-mail: fejj gnome org
+Userid: fejj

Added: trunk/Makefile.am
==============================================================================
--- (empty file)
+++ trunk/Makefile.am	Sat May 10 17:53:05 2008
@@ -0,0 +1,4 @@
+## Process this file with automake to produce Makefile.in
+
+SUBDIRS = imap4 po
+

Added: trunk/NEWS
==============================================================================

Added: trunk/README
==============================================================================
--- (empty file)
+++ trunk/README	Sat May 10 17:53:05 2008
@@ -0,0 +1,3 @@
+This module is a stand-alone package for the Camel IMAP4 provider
+plugin that builds against the installed Evolution-Data-Server
+package.

Added: trunk/acinclude.m4
==============================================================================
--- (empty file)
+++ trunk/acinclude.m4	Sat May 10 17:53:05 2008
@@ -0,0 +1,31 @@
+# EVO_PTHREAD_CHECK
+AC_DEFUN([EVO_PTHREAD_CHECK],[
+	PTHREAD_LIB=""
+	AC_CHECK_LIB(pthread, pthread_create, PTHREAD_LIB="-lpthread",
+		[AC_CHECK_LIB(pthreads, pthread_create, PTHREAD_LIB="-lpthreads",
+		    [AC_CHECK_LIB(c_r, pthread_create, PTHREAD_LIB="-lc_r",
+			[AC_CHECK_LIB(pthread, __pthread_attr_init_system, PTHREAD_LIB="-lpthread",
+				[AC_CHECK_FUNC(pthread_create)]
+			)]
+		    )]
+		)]
+	)
+	AC_SUBST(PTHREAD_LIB)
+	AC_MSG_CHECKING([if pthread_t can be cast to a guint64 without loss of data])
+	CFLAGS_save="$CFLAGS"
+	CFLAGS="$CFLAGS $GLIB_CFLAGS"
+	AC_TRY_COMPILE(
+		[#include <pthread.h>
+		 #include <glibconfig.h>],
+		[char a[(sizeof(guint64)>=sizeof(pthread_t))?1:-1];
+		 guint64 l;
+		 pthread_t t;
+		 l = (guint64) t;],
+		[AC_DEFINE(HAVE_GUINT64_CASTABLE_PTHREAD_T,1,[Define to 1 if pthread_t can be cast to a guint64])
+		 AC_MSG_RESULT([yes])],
+		[AC_MSG_RESULT([no])]
+	)
+	CFLAGS="$CFLAGS_save"
+	AC_PROVIDE([EVO_PTHREAD_CHECK])
+])
+dnl -*- mode: autoconf -*-

Added: trunk/autogen.sh
==============================================================================
--- (empty file)
+++ trunk/autogen.sh	Sat May 10 17:53:05 2008
@@ -0,0 +1,22 @@
+#!/bin/sh
+# Run this to generate all the initial makefiles, etc.
+
+srcdir=`dirname $0`
+test -z "$srcdir" && srcdir=.
+
+PKG_NAME="camel-imap4"
+REQUIRED_AUTOMAKE_VERSION=1.6
+
+(test -f $srcdir/configure.ac \
+  && test -f $srcdir/ChangeLog \
+  && test -d $srcdir/imap4) || {
+    echo -n "**Error**: Directory "\`$srcdir\'" does not look like the"
+    echo " top-level $PKG_NAME directory"
+    exit 1
+}
+
+which gnome-autogen.sh || {
+    echo "You need to install gnome-common from the GNOME CVS"
+    exit 1
+}
+USE_GNOME2_MACROS=1 . gnome-autogen.sh

Added: trunk/camel/camel-private.h
==============================================================================
--- (empty file)
+++ trunk/camel/camel-private.h	Sat May 10 17:53:05 2008
@@ -0,0 +1,212 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *  camel-private.h: Private info for class implementers.
+ *
+ * Authors: 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 Lesser 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
+ * USA
+ */
+
+#ifndef CAMEL_PRIVATE_H
+#define CAMEL_PRIVATE_H 1
+
+/* need a way to configure and save this data, if this header is to
+   be installed.  For now, dont install it */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <pthread.h>
+#include <libedataserver/e-msgport.h>
+
+G_BEGIN_DECLS
+
+struct _CamelFolderPrivate {
+	GStaticRecMutex lock;
+	GStaticMutex change_lock;
+	/* must require the 'change_lock' to access this */
+	int frozen;
+	struct _CamelFolderChangeInfo *changed_frozen; /* queues changed events */
+};
+
+#define CAMEL_FOLDER_LOCK(f, l) \
+	(g_static_mutex_lock(&((CamelFolder *) (f))->priv->l))
+#define CAMEL_FOLDER_UNLOCK(f, l) \
+	(g_static_mutex_unlock(&((CamelFolder *) (f))->priv->l))
+#define CAMEL_FOLDER_REC_LOCK(f, l) \
+	(g_static_rec_mutex_lock(&((CamelFolder *) (f))->priv->l))
+#define CAMEL_FOLDER_REC_UNLOCK(f, l) \
+	(g_static_rec_mutex_unlock(&((CamelFolder *) (f))->priv->l))
+
+
+struct _CamelStorePrivate {
+	GStaticRecMutex folder_lock;	/* for locking folder operations */
+};
+
+#define CAMEL_STORE_LOCK(f, l) \
+	(g_static_rec_mutex_lock(&((CamelStore *) (f))->priv->l))
+#define CAMEL_STORE_UNLOCK(f, l) \
+	(g_static_rec_mutex_unlock(&((CamelStore *) (f))->priv->l))
+
+
+struct _CamelTransportPrivate {
+	GMutex *send_lock;   /* for locking send operations */
+};
+
+#define CAMEL_TRANSPORT_LOCK(f, l) \
+	(g_mutex_lock(((CamelTransport *) (f))->priv->l))
+#define CAMEL_TRANSPORT_UNLOCK(f, l) \
+	(g_mutex_unlock(((CamelTransport *) (f))->priv->l))
+
+
+struct _CamelServicePrivate {
+	GStaticRecMutex connect_lock;	/* for locking connection operations */
+	GStaticMutex connect_op_lock;	/* for locking the connection_op */
+};
+
+#define CAMEL_SERVICE_LOCK(f, l) \
+	(g_static_mutex_lock(&((CamelService *) (f))->priv->l))
+#define CAMEL_SERVICE_UNLOCK(f, l) \
+	(g_static_mutex_unlock(&((CamelService *) (f))->priv->l))
+#define CAMEL_SERVICE_REC_LOCK(f, l) \
+	(g_static_rec_mutex_lock(&((CamelService *) (f))->priv->l))
+#define CAMEL_SERVICE_REC_UNLOCK(f, l) \
+	(g_static_rec_mutex_unlock(&((CamelService *) (f))->priv->l))
+#define CAMEL_SERVICE_REC_TRYLOCK(f, l) \
+	(g_static_rec_mutex_trylock(&((CamelService *) (f))->priv->l))
+
+
+struct _CamelSessionPrivate {
+	GMutex *lock;		/* for locking everything basically */
+	GMutex *thread_lock;	/* locking threads */
+
+	int thread_id;
+	GHashTable *thread_active;
+	GThreadPool *thread_pool;
+
+	GHashTable *thread_msg_op;
+	GHashTable *junk_headers;
+	
+};
+
+#define CAMEL_SESSION_LOCK(f, l) \
+	(g_mutex_lock(((CamelSession *) (f))->priv->l))
+#define CAMEL_SESSION_UNLOCK(f, l) \
+	(g_mutex_unlock(((CamelSession *) (f))->priv->l))
+
+
+/* most of this stuff really is private, but the lock can be used by subordinate classes */
+struct _CamelFolderSummaryPrivate {
+	GHashTable *filter_charset;	/* CamelMimeFilterCharset's indexed by source charset */
+
+	struct _CamelMimeFilterIndex *filter_index;
+	struct _CamelMimeFilterBasic *filter_64;
+	struct _CamelMimeFilterBasic *filter_qp;
+	struct _CamelMimeFilterBasic *filter_uu;
+	struct _CamelMimeFilterSave *filter_save;
+	struct _CamelMimeFilterHTML *filter_html;
+
+	struct _CamelStreamFilter *filter_stream;
+
+	struct _CamelIndex *index;
+	
+	GMutex *summary_lock;	/* for the summary hashtable/array */
+	GMutex *io_lock;	/* load/save lock, for access to saved_count, etc */
+	GMutex *filter_lock;	/* for accessing any of the filtering/indexing stuff, since we share them */
+	GMutex *alloc_lock;	/* for setting up and using allocators */
+	GMutex *ref_lock;	/* for reffing/unreffing messageinfo's ALWAYS obtain before summary_lock */
+};
+
+#define CAMEL_SUMMARY_LOCK(f, l) \
+	(g_mutex_lock(((CamelFolderSummary *) (f))->priv->l))
+#define CAMEL_SUMMARY_UNLOCK(f, l) \
+	(g_mutex_unlock(((CamelFolderSummary *) (f))->priv->l))
+
+
+struct _CamelStoreSummaryPrivate {
+	GMutex *summary_lock;	/* for the summary hashtable/array */
+	GMutex *io_lock;	/* load/save lock, for access to saved_count, etc */
+	GMutex *alloc_lock;	/* for setting up and using allocators */
+	GMutex *ref_lock;	/* for reffing/unreffing messageinfo's ALWAYS obtain before summary_lock */
+};
+
+#define CAMEL_STORE_SUMMARY_LOCK(f, l) \
+	(g_mutex_lock(((CamelStoreSummary *) (f))->priv->l))
+#define CAMEL_STORE_SUMMARY_UNLOCK(f, l) \
+	(g_mutex_unlock(((CamelStoreSummary *) (f))->priv->l))
+
+
+struct _CamelVeeFolderPrivate {
+	gboolean destroyed;
+	GList *folders;			/* lock using subfolder_lock before changing/accessing */
+	GList *folders_changed;		/* for list of folders that have changed between updates */
+	
+	GMutex *summary_lock;		/* for locking vfolder summary */
+	GMutex *subfolder_lock;		/* for locking the subfolder list */
+	GMutex *changed_lock;		/* for locking the folders-changed list */
+};
+
+#define CAMEL_VEE_FOLDER_LOCK(f, l) \
+	(g_mutex_lock(((CamelVeeFolder *) (f))->priv->l))
+#define CAMEL_VEE_FOLDER_UNLOCK(f, l) \
+	(g_mutex_unlock(((CamelVeeFolder *) (f))->priv->l))
+
+
+struct _CamelDataWrapperPrivate {
+	pthread_mutex_t stream_lock;
+};
+
+#define CAMEL_DATA_WRAPPER_LOCK(dw, l) \
+	(pthread_mutex_lock(&((CamelDataWrapper *) (dw))->priv->l))
+#define CAMEL_DATA_WRAPPER_UNLOCK(dw, l) \
+	(pthread_mutex_unlock(&((CamelDataWrapper *) (dw))->priv->l))
+
+
+/* most of this stuff really is private, but the lock can be used by subordinate classes */
+struct _CamelCertDBPrivate {
+	GMutex *db_lock;	/* for the db hashtable/array */
+	GMutex *io_lock;	/* load/save lock, for access to saved_count, etc */
+	GMutex *alloc_lock;	/* for setting up and using allocators */
+	GMutex *ref_lock;	/* for reffing/unreffing certs */
+};
+
+#define CAMEL_CERTDB_LOCK(db, l) \
+	(g_mutex_lock (((CamelCertDB *) (db))->priv->l))
+#define CAMEL_CERTDB_UNLOCK(db, l) \
+	(g_mutex_unlock (((CamelCertDB *) (db))->priv->l))
+
+#ifdef G_OS_WIN32
+int fsync (int fd);
+
+const char *_camel_get_localedir (void) G_GNUC_CONST;
+const char *_camel_get_libexecdir (void) G_GNUC_CONST;
+const char *_camel_get_providerdir (void) G_GNUC_CONST;
+
+#undef EVOLUTION_LOCALEDIR
+#define EVOLUTION_LOCALEDIR _camel_get_localedir ()
+
+#undef CAMEL_LIBEXECDIR
+#define CAMEL_LIBEXECDIR _camel_get_libexecdir ()
+
+#undef CAMEL_PROVIDERDIR
+#define CAMEL_PROVIDERDIR _camel_get_providerdir ()
+
+#endif /* G_OS_WIN32 */
+
+G_END_DECLS
+
+#endif /* CAMEL_PRIVATE_H */

Added: trunk/configure.ac
==============================================================================
--- (empty file)
+++ trunk/configure.ac	Sat May 10 17:53:05 2008
@@ -0,0 +1,179 @@
+ Process this file with autoconf to produce a configure script.
+AC_PREREQ(2.52)
+
+AC_INIT(camel-imap4, 2.23.2)
+AC_CONFIG_SRCDIR(README)
+
+# Gross hack to enable 'make dist' on automake 1.9+tar 1.14.
+# The extra brackets are to foil regex-based scans.
+m4_ifdef([_A][M_PROG_TAR],[_A][M_SET_OPTION([tar-ustar])])
+
+AM_INIT_AUTOMAKE(AC_PACKAGE_NAME, AC_PACKAGE_VERSION)
+
+AM_CONFIG_HEADER(config.h)
+
+dnl Put the ACLOCAL flags in the Makefile
+ACLOCAL="$ACLOCAL $ACLOCAL_FLAGS"
+
+dnl Initialize maintainer mode
+AM_MAINTAINER_MODE
+
+AC_ISC_POSIX
+AC_PROG_CC
+AC_PROG_CPP
+AC_C_INLINE
+AC_STDC_HEADERS
+AC_PROG_INSTALL
+AC_PROG_LN_S
+AC_PROG_MAKE_SET
+
+AM_GLIB_GNU_GETTEXT
+
+AC_ARG_ENABLE(ssl, AC_HELP_STRING([--enable-ssl],[Enable SSL support]),,
+              enable_ssl=no)
+if test "x$enable_ssl" = "xyes"; then
+  AC_DEFINE(HAVE_SSL, 1, [Define if Evolution-Data-Server was built with SSL support])
+fi
+
+dnl Utility macro to set compiler flags for a specific lib.
+dnl  EVO_SET_COMPILE_FLAGS(VAR-PREFIX, DEPS, EXTRA-CFLAGS, EXTRA-LIBS)
+AC_DEFUN([EVO_SET_COMPILE_FLAGS], [
+	PKG_CHECK_MODULES([$1], [$2])
+	$1_CFLAGS="[$]$1_CFLAGS \$(WERROR) $3"
+	$1_LIBS="[$]$1_LIBS $4"
+])
+
+GETTEXT_PACKAGE=camel-imap4
+AC_SUBST(GETTEXT_PACKAGE)
+AC_DEFINE_UNQUOTED(GETTEXT_PACKAGE, "$GETTEXT_PACKAGE", [Package name for gettext])
+
+localedir='$(prefix)/$(DATADIRNAME)/locale'
+AC_SUBST(localedir)
+
+dnl Initialize libtool
+AM_DISABLE_STATIC
+AC_LIBTOOL_WIN32_DLL
+AM_PROG_LIBTOOL
+
+PKG_PROG_PKG_CONFIG
+
+AC_CHECK_HEADERS(pthread.h semaphore.h sys/wait.h)
+AC_CHECK_FUNCS(fsync strptime strtok_r)
+
+dnl alloca()
+AC_CHECK_HEADERS(alloca.h)
+
+# Check for base dependencies early.
+PKG_CHECK_MODULES(GNOME_PLATFORM, [glib-2.0 >= 2.14.0 camel-1.2 >= 1.12.0 camel-provider-1.2 >= 1.12.0])
+
+dnl check for socklen_t (in Unix98)
+AC_MSG_CHECKING(for socklen_t)
+AC_TRY_COMPILE([#include <sys/types.h>
+#include <sys/socket.h>
+socklen_t x;
+],[],[AC_MSG_RESULT(yes)],[
+AC_TRY_COMPILE([#include <sys/types.h>
+#include <sys/socket.h>
+int accept (int, struct sockaddr *, size_t *);
+],[],[
+AC_MSG_RESULT(size_t)
+AC_DEFINE(socklen_t,size_t,[Define to appropriate type if socklen_t is not defined])], [
+AC_MSG_RESULT(int)
+AC_DEFINE(socklen_t,int)])])
+
+
+dnl ***************
+dnl Timezone checks
+dnl ***************
+AC_CACHE_CHECK(for tm_gmtoff in struct tm, ac_cv_struct_tm_gmtoff,
+	AC_TRY_COMPILE([
+		#include <time.h>
+		], [
+		struct tm tm;
+		tm.tm_gmtoff = 1;
+		], ac_cv_struct_tm_gmtoff=yes, ac_cv_struct_tm_gmtoff=no))
+if test $ac_cv_struct_tm_gmtoff = yes; then
+	AC_DEFINE(HAVE_TM_GMTOFF, 1, [Define if struct tm has a tm_gmtoff member])
+else
+	AC_CACHE_CHECK(for timezone variable, ac_cv_var_timezone,
+		AC_TRY_COMPILE([
+			#include <time.h>
+		], [
+			timezone = 1;
+		], ac_cv_var_timezone=yes, ac_cv_var_timezone=no))
+	if test $ac_cv_var_timezone = yes; then
+		AC_DEFINE(HAVE_TIMEZONE, 1, [Define if libc defines a timezone variable])
+		AC_CACHE_CHECK(for altzone variable, ac_cv_var_altzone,
+			AC_TRY_COMPILE([
+				#include <time.h>
+			], [
+				altzone = 1;
+			], ac_cv_var_altzone=yes, ac_cv_var_altzone=no))
+		if test $ac_cv_var_altzone = yes; then
+			AC_DEFINE(HAVE_ALTZONE, 1, [Define if libc defines an altzone variable])
+		fi
+	else
+		AC_ERROR(unable to find a way to determine timezone)
+	fi
+fi
+
+dnl *******************
+dnl GObject marshalling
+dnl *******************
+AM_PATH_GLIB_2_0
+
+dnl *************************
+dnl CFLAGS and LIBS and stuff
+dnl *************************
+
+GNOME_COMPILE_WARNINGS(yes)
+CFLAGS="$CFLAGS $WARN_CFLAGS"
+case $CFLAGS in
+*-Wall*)
+	# Turn off the annoying "comparison between signed and unsigned"
+	# warning in gcc 3.3
+	if $CC --help -v 2>&1 | grep Wsign-compare >/dev/null; then
+		CFLAGS="$CFLAGS -Wno-sign-compare"
+	fi
+	if $CC --help -v 2>&1 | grep Wpointer-sign >/dev/null; then
+		CFLAGS="$CFLAGS -Wno-pointer-sign"
+	fi
+	;;
+esac
+
+EVO_PTHREAD_CHECK
+
+THREADS_LIBS="$PTHREAD_LIB"
+THREADS_CFLAGS="$PTHREAD_CFLAGS"
+
+AC_SUBST(THREADS_LIBS)
+AC_SUBST(THREADS_CFLAGS)
+AC_DEFINE(ENABLE_THREADS,1,[Required])
+
+dnl ****************************************
+dnl Flags for the various libraries we build
+dnl ****************************************
+
+dnl --- Camel flags
+
+EVO_SET_COMPILE_FLAGS(CAMEL, glib-2.0 camel-1.2 camel-provider-1.2, $THREADS_CFLAGS, $THREADS_LIBS)
+AC_SUBST(CAMEL_CFLAGS)
+AC_SUBST(CAMEL_LIBS)
+
+camel_providerdir=`pkg-config --variable=camel_providerdir camel-provider-1.2`
+AC_SUBST(camel_providerdir)
+
+dnl ******************************
+dnl Makefiles
+dnl ******************************
+
+AC_OUTPUT([
+Makefile
+imap4/Makefile
+])
+
+echo "
+	camel-imap4 has been configured as follows:
+
+	Camel Provider dir: $camel_providerdir
+"

Added: trunk/imap4/Makefile.am
==============================================================================
--- (empty file)
+++ trunk/imap4/Makefile.am	Sat May 10 17:53:05 2008
@@ -0,0 +1,38 @@
+## Process this file with automake to produce Makefile.in
+
+camel_provider_LTLIBRARIES = libcamelimap4.la
+camel_provider_DATA = libcamelimap4.urls
+
+INCLUDES = -I$(top_srcdir) $(CAMEL_CFLAGS) \
+	-DG_LOG_DOMAIN=\"camel-imap4-provider\"
+
+libcamelimap4_la_SOURCES = 			\
+	camel-imap4-command.c			\
+	camel-imap4-command.h			\
+	camel-imap4-engine.c			\
+	camel-imap4-engine.h			\
+	camel-imap4-folder.c			\
+	camel-imap4-folder.h			\
+	camel-imap4-journal.c			\
+	camel-imap4-journal.h			\
+	camel-imap4-provider.c			\
+	camel-imap4-search.c			\
+	camel-imap4-search.h			\
+	camel-imap4-specials.c			\
+	camel-imap4-specials.h			\
+	camel-imap4-store.c			\
+	camel-imap4-store.h			\
+	camel-imap4-store-summary.c		\
+	camel-imap4-store-summary.h		\
+	camel-imap4-stream.c			\
+	camel-imap4-stream.h			\
+	camel-imap4-summary.c			\
+	camel-imap4-summary.h			\
+	camel-imap4-utils.c			\
+	camel-imap4-utils.h
+
+libcamelimap4_la_LDFLAGS = -avoid-version -module $(NO_UNDEFINED)
+
+libcamelimap4_la_LIBADD = $(CAMEL_LIBS)
+
+EXTRA_DIST = libcamelimap4.urls

Added: trunk/imap4/camel-imap4-command.c
==============================================================================
--- (empty file)
+++ trunk/imap4/camel-imap4-command.c	Sat May 10 17:53:05 2008
@@ -0,0 +1,734 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*  Camel
+ *  Copyright (C) 1999-2007 Novell, Inc. (www.novell.com)
+ *
+ *  Authors: Jeffrey Stedfast <fejj novell com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU Lesser General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  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 Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+
+#include <glib/gi18n-lib.h>
+
+#include <camel/camel-debug.h>
+#include <camel/camel-mime-filter-crlf.h>
+#include <camel/camel-stream-filter.h>
+#include <camel/camel-stream-null.h>
+
+#include "camel-imap4-command.h"
+#include "camel-imap4-engine.h"
+#include "camel-imap4-folder.h"
+#include "camel-imap4-specials.h"
+#include "camel-imap4-stream.h"
+
+#define d(x) (camel_debug ("imap4:command") ? (x) : 0)
+
+
+enum {
+	IMAP4_STRING_ATOM,
+	IMAP4_STRING_QSTRING,
+	IMAP4_STRING_LITERAL,
+};
+
+static int
+imap4_string_get_type (const char *str)
+{
+	int type = 0;
+	
+	while (*str) {
+		if (!is_atom (*str)) {
+			if (is_qsafe (*str))
+				type = IMAP4_STRING_QSTRING;
+			else
+				return IMAP4_STRING_LITERAL;
+		}
+		str++;
+	}
+	
+	return type;
+}
+
+#if 0
+static gboolean
+imap4_string_is_atom_safe (const char *str)
+{
+	while (is_atom (*str))
+		str++;
+	
+	return *str == '\0';
+}
+
+static gboolean
+imap4_string_is_quote_safe (const char *str)
+{
+	while (is_qsafe (*str))
+		str++;
+	
+	return *str == '\0';
+}
+#endif
+
+static size_t
+camel_imap4_literal_length (CamelIMAP4Literal *literal)
+{
+	CamelStream *stream, *null;
+	CamelMimeFilter *crlf;
+	size_t len;
+	
+	if (literal->type == CAMEL_IMAP4_LITERAL_STRING)
+		return strlen (literal->literal.string);
+	
+	null = camel_stream_null_new ();
+	crlf = camel_mime_filter_crlf_new (CAMEL_MIME_FILTER_CRLF_ENCODE, CAMEL_MIME_FILTER_CRLF_MODE_CRLF_ONLY);
+	stream = (CamelStream *) camel_stream_filter_new_with_stream (null);
+	camel_stream_filter_add ((CamelStreamFilter *) stream, crlf);
+	camel_object_unref (crlf);
+	
+	switch (literal->type) {
+	case CAMEL_IMAP4_LITERAL_STREAM:
+		camel_stream_write_to_stream (literal->literal.stream, stream);
+		camel_stream_reset (literal->literal.stream);
+		break;
+	case CAMEL_IMAP4_LITERAL_WRAPPER:
+		camel_data_wrapper_write_to_stream (literal->literal.wrapper, stream);
+		break;
+	default:
+		g_assert_not_reached ();
+		break;
+	}
+	
+	len = ((CamelStreamNull *) null)->written;
+	
+	camel_object_unref (stream);
+	camel_object_unref (null);
+	
+	return len;
+}
+
+static CamelIMAP4CommandPart *
+command_part_new (void)
+{
+	CamelIMAP4CommandPart *part;
+	
+	part = g_new (CamelIMAP4CommandPart, 1);
+	part->next = NULL;
+	part->buffer = NULL;
+	part->buflen = 0;
+	part->literal = NULL;
+	
+	return part;
+}
+
+static void
+imap4_command_append_string (CamelIMAP4Engine *engine, CamelIMAP4CommandPart **tail, GString *str, const char *string)
+{
+	CamelIMAP4CommandPart *part;
+	CamelIMAP4Literal *literal;
+	register const char *inptr;
+	const char *start;
+	
+	switch (imap4_string_get_type (string)) {
+	case IMAP4_STRING_ATOM:
+		/* string is safe as it is... */
+		g_string_append (str, string);
+		break;
+	case IMAP4_STRING_QSTRING:
+		/* we need to quote the string */
+		g_string_append_c (str, '"');
+		
+		inptr = string;
+		while (*inptr) {
+			start = string;
+			while (*inptr && *inptr != '\\' && *inptr != '"')
+				inptr++;
+			
+			if (inptr > start)
+				g_string_append_len (str, start, inptr - start);
+			
+			if (*inptr != '\0') {
+				g_string_append_c (str, '\\');
+				g_string_append_c (str, *inptr);
+				inptr++;
+			}
+		}
+		
+		g_string_append_c (str, '"');
+		break;
+	case IMAP4_STRING_LITERAL:
+		if (engine->capa & CAMEL_IMAP4_CAPABILITY_LITERALPLUS) {
+			/* we have to send a literal, but the server supports LITERAL+ so use that */
+			g_string_append_printf (str, "{%u+}\r\n%s", strlen (string), string);
+		} else {
+			/* we have to make it a literal */
+			literal = g_new (CamelIMAP4Literal, 1);
+			literal->type = CAMEL_IMAP4_LITERAL_STRING;
+			literal->literal.string = g_strdup (string);
+			
+			g_string_append_printf (str, "{%u}\r\n", strlen (string));
+			
+			(*tail)->buffer = g_strdup (str->str);
+			(*tail)->buflen = str->len;
+			(*tail)->literal = literal;
+			
+			part = command_part_new ();
+			(*tail)->next = part;
+			(*tail) = part;
+			
+			g_string_truncate (str, 0);
+		}
+		break;
+	}
+}
+
+CamelIMAP4Command *
+camel_imap4_command_newv (CamelIMAP4Engine *engine, CamelIMAP4Folder *imap4_folder, const char *format, va_list args)
+{
+	CamelIMAP4CommandPart *parts, *part, *tail;
+	CamelIMAP4Command *ic;
+	const char *start;
+	GString *str;
+	
+	tail = parts = command_part_new ();
+	
+	str = g_string_new ("");
+	start = format;
+	
+	while (*format) {
+		register char ch = *format++;
+		
+		if (ch == '%') {
+			CamelIMAP4Literal *literal;
+			CamelIMAP4Folder *folder;
+			char *function, **strv;
+			unsigned int u;
+			char *string;
+			size_t len;
+			void *obj;
+			int c, d;
+			
+			g_string_append_len (str, start, format - start - 1);
+			
+			switch (*format) {
+			case '%':
+				/* literal % */
+				g_string_append_c (str, '%');
+				break;
+			case 'c':
+				/* character */
+				c = va_arg (args, int);
+				g_string_append_c (str, c);
+				break;
+			case 'd':
+				/* integer */
+				d = va_arg (args, int);
+				g_string_append_printf (str, "%d", d);
+				break;
+			case 'u':
+				/* unsigned integer */
+				u = va_arg (args, unsigned int);
+				g_string_append_printf (str, "%u", u);
+				break;
+			case 'F':
+				/* CamelIMAP4Folder */
+				folder = va_arg (args, CamelIMAP4Folder *);
+				string = (char *) camel_imap4_folder_utf7_name (folder);
+				imap4_command_append_string (engine, &tail, str, string);
+				break;
+			case 'L':
+				/* Literal */
+				obj = va_arg (args, void *);
+				
+				literal = g_new (CamelIMAP4Literal, 1);
+				if (CAMEL_IS_DATA_WRAPPER (obj)) {
+					literal->type = CAMEL_IMAP4_LITERAL_WRAPPER;
+					literal->literal.wrapper = obj;
+				} else if (CAMEL_IS_STREAM (obj)) {
+					literal->type = CAMEL_IMAP4_LITERAL_STREAM;
+					literal->literal.stream = obj;
+				} else {
+					g_assert_not_reached ();
+				}
+				
+				camel_object_ref (obj);
+				
+				/* FIXME: take advantage of LITERAL+? */
+				len = camel_imap4_literal_length (literal);
+				g_string_append_printf (str, "{%u}\r\n", len);
+				
+				tail->buffer = g_strdup (str->str);
+				tail->buflen = str->len;
+				tail->literal = literal;
+				
+				part = command_part_new ();
+				tail->next = part;
+				tail = part;
+				
+				g_string_truncate (str, 0);
+				
+				break;
+			case 'V':
+				/* a string vector of arguments which may need to be quoted or made into literals */
+				function = str->str + str->len - 2;
+				while (*function != ' ')
+					function--;
+				function++;
+				
+				function = g_strdup (function);
+				
+				strv = va_arg (args, char **);
+				for (d = 0; strv[d]; d++) {
+					if (d > 0)
+						g_string_append (str, function);
+					imap4_command_append_string (engine, &tail, str, strv[d]);
+				}
+				
+				g_free (function);
+				break;
+			case 'S':
+				/* string which may need to be quoted or made into a literal */
+				string = va_arg (args, char *);
+				imap4_command_append_string (engine, &tail, str, string);
+				break;
+			case 's':
+				/* safe atom string */
+				string = va_arg (args, char *);
+				g_string_append (str, string);
+				break;
+			default:
+				g_warning ("unknown formatter %%%c", *format);
+				g_string_append_c (str, '%');
+				g_string_append_c (str, *format);
+				break;
+			}
+			
+			format++;
+			
+			start = format;
+		}
+	}
+	
+	g_string_append (str, start);
+	tail->buffer = str->str;
+	tail->buflen = str->len;
+	tail->literal = NULL;
+	g_string_free (str, FALSE);
+	
+	ic = g_new0 (CamelIMAP4Command, 1);
+	((EDListNode *) ic)->next = NULL;
+	((EDListNode *) ic)->prev = NULL;
+	ic->untagged = g_hash_table_new (g_str_hash, g_str_equal);
+	ic->status = CAMEL_IMAP4_COMMAND_QUEUED;
+	ic->result = CAMEL_IMAP4_RESULT_NONE;
+	ic->resp_codes = g_ptr_array_new ();
+	ic->engine = engine;
+	ic->ref_count = 1;
+	ic->parts = parts;
+	ic->part = parts;
+	ic->reset = NULL;
+	ic->plus = NULL;
+	ic->tag = NULL;
+	ic->id = -1;
+	
+	camel_exception_init (&ic->ex);
+	
+	ic->user_data = NULL;
+	
+	if (imap4_folder) {
+		camel_object_ref (imap4_folder);
+		ic->folder = imap4_folder;
+	} else
+		ic->folder = NULL;
+	
+	return ic;
+}
+
+CamelIMAP4Command *
+camel_imap4_command_new (CamelIMAP4Engine *engine, CamelIMAP4Folder *folder, const char *format, ...)
+{
+	CamelIMAP4Command *command;
+	va_list args;
+	
+	va_start (args, format);
+	command = camel_imap4_command_newv (engine, folder, format, args);
+	va_end (args);
+	
+	return command;
+}
+
+void
+camel_imap4_command_register_untagged (CamelIMAP4Command *ic, const char *atom, CamelIMAP4UntaggedCallback untagged)
+{
+	g_hash_table_insert (ic->untagged, g_strdup (atom), untagged);
+}
+
+void
+camel_imap4_command_ref (CamelIMAP4Command *ic)
+{
+	ic->ref_count++;
+}
+
+void
+camel_imap4_command_unref (CamelIMAP4Command *ic)
+{
+	CamelIMAP4CommandPart *part, *next;
+	int i;
+	
+	if (ic == NULL)
+		return;
+	
+	ic->ref_count--;
+	if (ic->ref_count == 0) {
+		if (ic->folder)
+			camel_object_unref (ic->folder);
+		
+		g_free (ic->tag);
+		
+		for (i = 0; i < ic->resp_codes->len; i++) {
+			CamelIMAP4RespCode *resp_code;
+			
+			resp_code = ic->resp_codes->pdata[i];
+			camel_imap4_resp_code_free (resp_code);
+		}
+		g_ptr_array_free (ic->resp_codes, TRUE);
+		
+		g_hash_table_foreach (ic->untagged, (GHFunc) g_free, NULL);
+		g_hash_table_destroy (ic->untagged);
+		
+		camel_exception_clear (&ic->ex);
+		
+		part = ic->parts;
+		while (part != NULL) {
+			g_free (part->buffer);
+			if (part->literal) {
+				switch (part->literal->type) {
+				case CAMEL_IMAP4_LITERAL_STRING:
+					g_free (part->literal->literal.string);
+					break;
+				case CAMEL_IMAP4_LITERAL_STREAM:
+					camel_object_unref (part->literal->literal.stream);
+					break;
+				case CAMEL_IMAP4_LITERAL_WRAPPER:
+					camel_object_unref (part->literal->literal.wrapper);
+					break;
+				}
+				
+				g_free (part->literal);
+			}
+			
+			next = part->next;
+			g_free (part);
+			part = next;
+		}
+		
+		g_free (ic);
+	}
+}
+
+
+static int
+imap4_literal_write_to_stream (CamelIMAP4Literal *literal, CamelStream *stream)
+{
+	CamelStream *istream, *ostream = NULL;
+	CamelDataWrapper *wrapper;
+	CamelMimeFilter *crlf;
+	char *string;
+	
+	if (literal->type == CAMEL_IMAP4_LITERAL_STRING) {
+		string = literal->literal.string;
+		if (camel_stream_write (stream, string, strlen (string)) == -1)
+			return -1;
+		
+		return 0;
+	}
+	
+	crlf = camel_mime_filter_crlf_new (CAMEL_MIME_FILTER_CRLF_ENCODE, CAMEL_MIME_FILTER_CRLF_MODE_CRLF_ONLY);
+	ostream = (CamelStream *) camel_stream_filter_new_with_stream (stream);
+	camel_stream_filter_add ((CamelStreamFilter *) ostream, crlf);
+	camel_object_unref (crlf);
+	
+	/* write the literal */
+	switch (literal->type) {
+	case CAMEL_IMAP4_LITERAL_STREAM:
+		istream = literal->literal.stream;
+		if (camel_stream_write_to_stream (istream, ostream) == -1)
+			goto exception;
+		break;
+	case CAMEL_IMAP4_LITERAL_WRAPPER:
+		wrapper = literal->literal.wrapper;
+		if (camel_data_wrapper_write_to_stream (wrapper, ostream) == -1)
+			goto exception;
+		break;
+	}
+	
+	camel_object_unref (ostream);
+	ostream = NULL;
+	
+#if 0
+	if (camel_stream_write (stream, "\r\n", 2) == -1)
+		return -1;
+#endif
+	
+	return 0;
+	
+ exception:
+	
+	camel_object_unref (ostream);
+	
+	return -1;
+}
+
+
+static void
+unexpected_token (camel_imap4_token_t *token)
+{
+	switch (token->token) {
+	case CAMEL_IMAP4_TOKEN_NO_DATA:
+		fprintf (stderr, "*** NO DATA ***");
+		break;
+	case CAMEL_IMAP4_TOKEN_ERROR:
+		fprintf (stderr, "*** ERROR ***");
+		break;
+	case CAMEL_IMAP4_TOKEN_NIL:
+		fprintf (stderr, "NIL");
+		break;
+	case CAMEL_IMAP4_TOKEN_ATOM:
+	        fprintf (stderr, "%s", token->v.atom);
+		break;
+	case CAMEL_IMAP4_TOKEN_QSTRING:
+	        fprintf (stderr, "\"%s\"", token->v.qstring);
+		break;
+	case CAMEL_IMAP4_TOKEN_LITERAL:
+		fprintf (stderr, "{%u}", token->v.literal);
+		break;
+	default:
+		fprintf (stderr, "%c", (unsigned char) (token->token & 0xff));
+		break;
+	}
+}
+
+int
+camel_imap4_command_step (CamelIMAP4Command *ic)
+{
+	CamelIMAP4Engine *engine = ic->engine;
+	int result = CAMEL_IMAP4_RESULT_NONE;
+	CamelIMAP4Literal *literal;
+	camel_imap4_token_t token;
+	unsigned char *linebuf;
+	size_t len;
+	
+	g_assert (ic->part != NULL);
+	
+	if (ic->part == ic->parts) {
+		ic->tag = g_strdup_printf ("%c%.5u", engine->tagprefix, engine->tag++);
+		camel_stream_printf (engine->ostream, "%s ", ic->tag);
+		d(fprintf (stderr, "sending: %s ", ic->tag));
+	}
+	
+	if (camel_debug ("imap4:command")) {
+		int sending = ic->part != ic->parts;
+		unsigned char *eoln, *eob;
+		
+		linebuf = ic->part->buffer;
+		eob = linebuf + ic->part->buflen;
+		
+		do {
+			eoln = linebuf;
+			while (eoln < eob && *eoln != '\n')
+				eoln++;
+			
+			if (eoln < eob)
+				eoln++;
+			
+			if (sending)
+				fwrite ("sending: ", 1, 10, stderr);
+			fwrite (linebuf, 1, eoln - linebuf, stderr);
+			
+			linebuf = eoln + 1;
+			sending = 1;
+		} while (linebuf < eob);
+	}
+	
+	linebuf = ic->part->buffer;
+	len = ic->part->buflen;
+	
+	if (camel_stream_write (engine->ostream, linebuf, len) == -1) {
+		camel_exception_setv (&ic->ex, CAMEL_EXCEPTION_SYSTEM,
+				      _("Failed sending command to IMAP server %s: %s"),
+				      engine->url->host, g_strerror (errno));
+		goto exception;
+	}
+	
+	if (camel_stream_flush (engine->ostream) == -1) {
+		camel_exception_setv (&ic->ex, CAMEL_EXCEPTION_SYSTEM,
+				      _("Failed sending command to IMAP server %s: %s"),
+				      engine->url->host, g_strerror (errno));
+		goto exception;
+	}
+	
+	/* now we need to read the response(s) from the IMAP4 server */
+	
+	do {
+		if (camel_imap4_engine_next_token (engine, &token, &ic->ex) == -1)
+			goto exception;
+		
+		if (token.token == '+') {
+			/* we got a continuation response from the server */
+			literal = ic->part->literal;
+			
+			if (camel_imap4_engine_line (engine, &linebuf, &len, &ic->ex) == -1)
+				goto exception;
+			
+			if (literal) {
+				if (imap4_literal_write_to_stream (literal, engine->ostream) == -1)
+					goto exception;
+				
+				g_free (linebuf);
+				linebuf = NULL;
+				
+				break;
+			} else if (ic->plus) {
+				/* command expected a '+' response - probably AUTHENTICATE? */
+				if (ic->plus (engine, ic, linebuf, len, &ic->ex) == -1) {
+					g_free (linebuf);
+					return -1;
+				}
+				
+				/* now we need to wait for a "<tag> OK/NO/BAD" response */
+			} else {
+				/* FIXME: error?? */
+				g_assert_not_reached ();
+			}
+			
+			g_free (linebuf);
+			linebuf = NULL;
+		} else if (token.token == '*') {
+			/* we got an untagged response, let the engine handle this */
+			if (camel_imap4_engine_handle_untagged_1 (engine, &token, &ic->ex) == -1)
+				goto exception;
+		} else if (token.token == CAMEL_IMAP4_TOKEN_ATOM && !strcmp (token.v.atom, ic->tag)) {
+			/* we got "<tag> OK/NO/BAD" */
+			d(fprintf (stderr, "got %s response\n", token.v.atom));
+			
+			if (camel_imap4_engine_next_token (engine, &token, &ic->ex) == -1)
+				goto exception;
+			
+			if (token.token == CAMEL_IMAP4_TOKEN_ATOM) {
+				if (!strcmp (token.v.atom, "OK"))
+					result = CAMEL_IMAP4_RESULT_OK;
+				else if (!strcmp (token.v.atom, "NO"))
+					result = CAMEL_IMAP4_RESULT_NO;
+				else if (!strcmp (token.v.atom, "BAD"))
+					result = CAMEL_IMAP4_RESULT_BAD;
+				
+				if (result == CAMEL_IMAP4_RESULT_NONE) {
+					d(fprintf (stderr, "expected OK/NO/BAD but got %s\n", token.v.atom));
+					goto unexpected;
+				}
+				
+				if (camel_imap4_engine_next_token (engine, &token, &ic->ex) == -1)
+					goto exception;
+				
+				if (token.token == '[') {
+					/* we have a response code */
+					camel_imap4_stream_unget_token (engine->istream, &token);
+					if (camel_imap4_engine_parse_resp_code (engine, &ic->ex) == -1)
+						goto exception;
+				} else if (token.token != '\n') {
+					/* just gobble up the rest of the line */
+					if (camel_imap4_engine_line (engine, NULL, NULL, &ic->ex) == -1)
+						goto exception;
+				}
+			} else {
+				if (camel_debug ("imap4:command")) {
+					fprintf (stderr, "expected anything but this: ");
+					unexpected_token (&token);
+					fprintf (stderr, "\n");
+				}
+				
+				goto unexpected;
+			}
+			
+			break;
+		} else {
+			if (camel_debug ("imap4:command")) {
+				fprintf (stderr, "wtf is this: ");
+				unexpected_token (&token);
+				fprintf (stderr, "\n");
+			}
+			
+		unexpected:
+			
+			/* no fucking clue what we got... */
+			if (camel_imap4_engine_line (engine, &linebuf, &len, &ic->ex) == -1)
+				goto exception;
+			
+			camel_exception_setv (&ic->ex, CAMEL_EXCEPTION_SYSTEM,
+					      _("Unexpected response from IMAP4 server %s: %s"),
+					      engine->url->host, linebuf);
+			
+			g_free (linebuf);
+			
+			goto exception;
+		}
+	} while (1);
+	
+	/* status should always be ACTIVE here... */
+	if (ic->status == CAMEL_IMAP4_COMMAND_ACTIVE) {
+		ic->part = ic->part->next;
+		if (ic->part == NULL || result) {
+			ic->status = CAMEL_IMAP4_COMMAND_COMPLETE;
+			ic->result = result;
+			return 1;
+		}
+	}
+	
+	return 0;
+	
+ exception:
+	
+	ic->status = CAMEL_IMAP4_COMMAND_ERROR;
+	
+	return -1;
+}
+
+
+void
+camel_imap4_command_reset (CamelIMAP4Command *ic)
+{
+	int i;
+	
+	for (i = 0; i < ic->resp_codes->len; i++)
+		camel_imap4_resp_code_free (ic->resp_codes->pdata[i]);
+	g_ptr_array_set_size (ic->resp_codes, 0);
+	
+	if (ic->reset && ic->user_data)
+		ic->reset (ic, ic->user_data);
+	
+	ic->status = CAMEL_IMAP4_COMMAND_QUEUED;
+	ic->result = CAMEL_IMAP4_RESULT_NONE;
+	ic->part = ic->parts;
+	g_free (ic->tag);
+	ic->tag = NULL;
+	
+	camel_exception_clear (&ic->ex);
+}

Added: trunk/imap4/camel-imap4-command.h
==============================================================================
--- (empty file)
+++ trunk/imap4/camel-imap4-command.h	Sat May 10 17:53:05 2008
@@ -0,0 +1,144 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*  Camel
+ *  Copyright (C) 1999-2007 Novell, Inc. (www.novell.com)
+ *
+ *  Authors: Jeffrey Stedfast <fejj novell com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU Lesser General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  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 Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+
+#ifndef __CAMEL_IMAP4_COMMAND_H__
+#define __CAMEL_IMAP4_COMMAND_H__
+
+#include <stdarg.h>
+
+#include <glib.h>
+
+#include <libedataserver/e-msgport.h>
+
+#include <camel/camel-stream.h>
+#include <camel/camel-exception.h>
+#include <camel/camel-data-wrapper.h>
+
+G_BEGIN_DECLS
+
+struct _CamelIMAP4Engine;
+struct _CamelIMAP4Folder;
+struct _camel_imap4_token_t;
+
+typedef struct _CamelIMAP4Command CamelIMAP4Command;
+typedef struct _CamelIMAP4Literal CamelIMAP4Literal;
+
+typedef int (* CamelIMAP4PlusCallback) (struct _CamelIMAP4Engine *engine,
+					CamelIMAP4Command *ic,
+					const unsigned char *linebuf,
+					size_t linelen, CamelException *ex);
+
+typedef int (* CamelIMAP4UntaggedCallback) (struct _CamelIMAP4Engine *engine,
+					    CamelIMAP4Command *ic,
+					    guint32 index,
+					    struct _camel_imap4_token_t *token,
+					    CamelException *ex);
+
+typedef void (* CamelIMAP4CommandReset) (CamelIMAP4Command *ic, void *user_data);
+
+enum {
+	CAMEL_IMAP4_LITERAL_STRING,
+	CAMEL_IMAP4_LITERAL_STREAM,
+	CAMEL_IMAP4_LITERAL_WRAPPER,
+};
+
+struct _CamelIMAP4Literal {
+	int type;
+	union {
+		char *string;
+		CamelStream *stream;
+		CamelDataWrapper *wrapper;
+	} literal;
+};
+
+typedef struct _CamelIMAP4CommandPart {
+	struct _CamelIMAP4CommandPart *next;
+	unsigned char *buffer;
+	size_t buflen;
+	
+	CamelIMAP4Literal *literal;
+} CamelIMAP4CommandPart;
+
+enum {
+	CAMEL_IMAP4_COMMAND_QUEUED,
+	CAMEL_IMAP4_COMMAND_ACTIVE,
+	CAMEL_IMAP4_COMMAND_COMPLETE,
+	CAMEL_IMAP4_COMMAND_ERROR,
+};
+
+enum {
+	CAMEL_IMAP4_RESULT_NONE,
+	CAMEL_IMAP4_RESULT_OK,
+	CAMEL_IMAP4_RESULT_NO,
+	CAMEL_IMAP4_RESULT_BAD,
+};
+
+struct _CamelIMAP4Command {
+	EDListNode node;
+	
+	struct _CamelIMAP4Engine *engine;
+	
+	unsigned int ref_count:26;
+	unsigned int status:3;
+	unsigned int result:3;
+	int id;
+	
+	char *tag;
+	
+	GPtrArray *resp_codes;
+	
+	struct _CamelIMAP4Folder *folder;
+	CamelException ex;
+	
+	/* command parts - logical breaks in the overall command based on literals */
+	CamelIMAP4CommandPart *parts;
+	
+	/* current part */
+	CamelIMAP4CommandPart *part;
+	
+	/* untagged handlers */
+	GHashTable *untagged;
+	
+	/* '+' callback/data */
+	CamelIMAP4PlusCallback plus;
+	CamelIMAP4CommandReset reset;
+	void *user_data;
+};
+
+CamelIMAP4Command *camel_imap4_command_new (struct _CamelIMAP4Engine *engine, struct _CamelIMAP4Folder *folder,
+					    const char *format, ...);
+CamelIMAP4Command *camel_imap4_command_newv (struct _CamelIMAP4Engine *engine, struct _CamelIMAP4Folder *folder,
+					     const char *format, va_list args);
+
+void camel_imap4_command_register_untagged (CamelIMAP4Command *ic, const char *atom, CamelIMAP4UntaggedCallback untagged);
+
+void camel_imap4_command_ref (CamelIMAP4Command *ic);
+void camel_imap4_command_unref (CamelIMAP4Command *ic);
+
+/* returns 1 when complete, 0 if there is more to do, or -1 on error */
+int camel_imap4_command_step (CamelIMAP4Command *ic);
+
+void camel_imap4_command_reset (CamelIMAP4Command *ic);
+
+G_END_DECLS
+
+#endif /* __CAMEL_IMAP4_COMMAND_H__ */

Added: trunk/imap4/camel-imap4-engine.c
==============================================================================
--- (empty file)
+++ trunk/imap4/camel-imap4-engine.c	Sat May 10 17:53:05 2008
@@ -0,0 +1,1784 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*  Camel
+ *  Copyright (C) 1999-2007 Novell, Inc. (www.novell.com)
+ *
+ *  Authors: Jeffrey Stedfast <fejj novell com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU Lesser General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  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 Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include <ctype.h>
+#include <errno.h>
+
+#include <glib/gi18n-lib.h>
+
+#include <camel/camel-sasl.h>
+#include <camel/camel-stream-buffer.h>
+
+#include "camel-imap4-command.h"
+#include "camel-imap4-engine.h"
+#include "camel-imap4-folder.h"
+#include "camel-imap4-stream.h"
+#include "camel-imap4-summary.h"
+#include "camel-imap4-utils.h"
+
+#define d(x) x
+
+
+static void camel_imap4_engine_class_init (CamelIMAP4EngineClass *klass);
+static void camel_imap4_engine_init (CamelIMAP4Engine *engine, CamelIMAP4EngineClass *klass);
+static void camel_imap4_engine_finalize (CamelObject *object);
+
+static int parse_xgwextensions (CamelIMAP4Engine *engine, CamelIMAP4Command *ic, guint32 index,
+				camel_imap4_token_t *token, CamelException *ex);
+
+
+static CamelObjectClass *parent_class = NULL;
+
+
+CamelType
+camel_imap4_engine_get_type (void)
+{
+	static CamelType type = 0;
+	
+	if (!type) {
+		type = camel_type_register (camel_object_get_type (),
+					    "CamelIMAP4Engine",
+					    sizeof (CamelIMAP4Engine),
+					    sizeof (CamelIMAP4EngineClass),
+					    (CamelObjectClassInitFunc) camel_imap4_engine_class_init,
+					    NULL,
+					    (CamelObjectInitFunc) camel_imap4_engine_init,
+					    (CamelObjectFinalizeFunc) camel_imap4_engine_finalize);
+	}
+	
+	return type;
+}
+
+static void
+camel_imap4_engine_class_init (CamelIMAP4EngineClass *klass)
+{
+	parent_class = camel_type_get_global_classfuncs (CAMEL_OBJECT_TYPE);
+	
+	klass->tagprefix = 'A';
+}
+
+static void
+camel_imap4_engine_init (CamelIMAP4Engine *engine, CamelIMAP4EngineClass *klass)
+{
+	engine->state = CAMEL_IMAP4_ENGINE_DISCONNECTED;
+	engine->level = CAMEL_IMAP4_LEVEL_UNKNOWN;
+	
+	engine->session = NULL;
+	engine->service = NULL;
+	engine->url = NULL;
+	
+	engine->istream = NULL;
+	engine->ostream = NULL;
+	
+	engine->authtypes = g_hash_table_new (g_str_hash, g_str_equal);
+	
+	engine->capa = 0;
+	
+	/* this is the suggested default, impacts the max command line length we'll send */
+	engine->maxlentype = CAMEL_IMAP4_ENGINE_MAXLEN_LINE;
+	engine->maxlen = 1000;
+	
+	engine->namespaces.personal = NULL;
+	engine->namespaces.other = NULL;
+	engine->namespaces.shared = NULL;
+	
+	if (klass->tagprefix > 'Z')
+		klass->tagprefix = 'A';
+	
+	engine->tagprefix = klass->tagprefix++;
+	engine->tag = 0;
+	
+	engine->nextid = 1;
+	
+	engine->folder = NULL;
+	
+	e_dlist_init (&engine->queue);
+}
+
+static void
+camel_imap4_engine_finalize (CamelObject *object)
+{
+	CamelIMAP4Engine *engine = (CamelIMAP4Engine *) object;
+	EDListNode *node;
+	
+	if (engine->istream)
+		camel_object_unref (engine->istream);
+	
+	if (engine->ostream)
+		camel_object_unref (engine->ostream);
+	
+	g_hash_table_foreach (engine->authtypes, (GHFunc) g_free, NULL);
+	g_hash_table_destroy (engine->authtypes);
+	
+	camel_imap4_namespace_clear (&engine->namespaces.personal);
+	camel_imap4_namespace_clear (&engine->namespaces.other);
+	camel_imap4_namespace_clear (&engine->namespaces.shared);
+	
+	if (engine->folder)
+		camel_object_unref (engine->folder);
+	
+	while ((node = e_dlist_remhead (&engine->queue))) {
+		node->next = NULL;
+		node->prev = NULL;
+		
+		camel_imap4_command_unref ((CamelIMAP4Command *) node);
+	}
+}
+
+
+/**
+ * camel_imap4_engine_new:
+ * @service: service
+ * @reconnect: reconnect callback function
+ *
+ * Returns a new imap4 engine
+ **/
+CamelIMAP4Engine *
+camel_imap4_engine_new (CamelService *service, CamelIMAP4ReconnectFunc reconnect)
+{
+	CamelIMAP4Engine *engine;
+	
+	g_return_val_if_fail (CAMEL_IS_SERVICE (service), NULL);
+	
+	engine = (CamelIMAP4Engine *) camel_object_new (CAMEL_TYPE_IMAP4_ENGINE);
+	engine->session = service->session;
+	engine->url = service->url;
+	engine->service = service;
+	engine->reconnect = reconnect;
+	
+	return engine;
+}
+
+
+/**
+ * camel_imap4_engine_take_stream:
+ * @engine: imap4 engine
+ * @stream: tcp stream
+ * @ex: exception
+ *
+ * Gives ownership of @stream to @engine and reads the greeting from
+ * the stream.
+ *
+ * Returns 0 on success or -1 on fail.
+ *
+ * Note: on error, @stream will be unref'd.
+ **/
+int
+camel_imap4_engine_take_stream (CamelIMAP4Engine *engine, CamelStream *stream, CamelException *ex)
+{
+	camel_imap4_token_t token;
+	int code;
+	
+	g_return_val_if_fail (CAMEL_IS_IMAP4_ENGINE (engine), -1);
+	g_return_val_if_fail (CAMEL_IS_STREAM (stream), -1);
+	
+	if (engine->istream)
+		camel_object_unref (engine->istream);
+	
+	if (engine->ostream)
+		camel_object_unref (engine->ostream);
+	
+	engine->istream = (CamelIMAP4Stream *) camel_imap4_stream_new (stream);
+	engine->ostream = camel_stream_buffer_new (stream, CAMEL_STREAM_BUFFER_WRITE);
+	engine->state = CAMEL_IMAP4_ENGINE_CONNECTED;
+	camel_object_unref (stream);
+	
+	if (camel_imap4_engine_next_token (engine, &token, ex) == -1)
+		goto exception;
+	
+	if (token.token != '*') {
+		camel_imap4_utils_set_unexpected_token_error (ex, engine, &token);
+		goto exception;
+	}
+	
+	if ((code = camel_imap4_engine_handle_untagged_1 (engine, &token, ex)) == -1) {
+		goto exception;
+	} else if (code != CAMEL_IMAP4_UNTAGGED_OK && code != CAMEL_IMAP4_UNTAGGED_PREAUTH) {
+		camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM, _("Unexpected greeting from IMAP server %s."),
+				      engine->url->host);
+		goto exception;
+	}
+	
+	return 0;
+	
+ exception:
+	
+	engine->state = CAMEL_IMAP4_ENGINE_DISCONNECTED;
+	
+	camel_object_unref (engine->istream);
+	engine->istream = NULL;
+	camel_object_unref (engine->ostream);
+	engine->ostream = NULL;
+	
+	return -1;
+}
+
+
+/**
+ * camel_imap4_engine_capability:
+ * @engine: IMAP4 engine
+ * @ex: exception
+ *
+ * Forces the IMAP4 engine to query the IMAP4 server for a list of capabilities.
+ *
+ * Returns 0 on success or -1 on fail.
+ **/
+int
+camel_imap4_engine_capability (CamelIMAP4Engine *engine, CamelException *ex)
+{
+	CamelIMAP4Command *ic;
+	int id, retval = 0;
+	
+	ic = camel_imap4_engine_prequeue (engine, NULL, "CAPABILITY\r\n");
+	
+	while ((id = camel_imap4_engine_iterate (engine)) < ic->id && id != -1)
+		;
+	
+	if (id == -1 || ic->status != CAMEL_IMAP4_COMMAND_COMPLETE) {
+		camel_exception_xfer (ex, &ic->ex);
+		retval = -1;
+	}
+	
+	camel_imap4_command_unref (ic);
+	
+	if (retval == -1 || !(engine->capa & CAMEL_IMAP4_CAPABILITY_XGWEXTENSIONS))
+		return retval;
+	
+	ic = camel_imap4_engine_prequeue (engine, NULL, "XGWEXTENSIONS\r\n");
+	camel_imap4_command_register_untagged (ic, "XGWEXTENSIONS", parse_xgwextensions);
+	
+	while ((id = camel_imap4_engine_iterate (engine)) < ic->id && id != -1)
+		;
+	
+	if (id == -1 || ic->status != CAMEL_IMAP4_COMMAND_COMPLETE) {
+		camel_exception_xfer (ex, &ic->ex);
+		retval = -1;
+	}
+	
+	camel_imap4_command_unref (ic);
+	
+	return retval;
+}
+
+
+/**
+ * camel_imap4_engine_namespace:
+ * @engine: IMAP4 engine
+ * @ex: exception
+ *
+ * Forces the IMAP4 engine to query the IMAP4 server for a list of namespaces.
+ *
+ * Returns 0 on success or -1 on fail.
+ **/
+int
+camel_imap4_engine_namespace (CamelIMAP4Engine *engine, CamelException *ex)
+{
+	camel_imap4_list_t *list;
+	GPtrArray *array = NULL;
+	CamelIMAP4Command *ic;
+	int id, i;
+	
+	if (engine->capa & CAMEL_IMAP4_CAPABILITY_NAMESPACE) {
+		ic = camel_imap4_engine_prequeue (engine, NULL, "NAMESPACE\r\n");
+	} else {
+		ic = camel_imap4_engine_prequeue (engine, NULL, "LIST \"\" \"\"\r\n");
+		camel_imap4_command_register_untagged (ic, "LIST", camel_imap4_untagged_list);
+		ic->user_data = array = g_ptr_array_new ();
+	}
+	
+	while ((id = camel_imap4_engine_iterate (engine)) < ic->id && id != -1)
+		;
+	
+	if (id == -1 || ic->status != CAMEL_IMAP4_COMMAND_COMPLETE) {
+		camel_exception_xfer (ex, &ic->ex);
+		camel_imap4_command_unref (ic);
+		
+		if (array != NULL)
+			g_ptr_array_free (array, TRUE);
+		
+		return -1;
+	}
+	
+	if (array != NULL) {
+		if (ic->result == CAMEL_IMAP4_RESULT_OK) {
+			CamelIMAP4Namespace *namespace;
+			
+			g_assert (array->len >= 1);
+			list = array->pdata[0];
+			
+			namespace = g_new (CamelIMAP4Namespace, 1);
+			namespace->next = NULL;
+			namespace->path = g_strdup ("");
+			namespace->sep = list->delim;
+			
+			engine->namespaces.personal = namespace;
+		} else {
+			/* should never *ever* happen */
+		}
+		
+		for (i = 0; i < array->len; i++) {
+			list = array->pdata[i];
+			g_free (list->name);
+			g_free (list);
+		}
+		
+		g_ptr_array_free (array, TRUE);
+	}
+	
+	camel_imap4_command_unref (ic);
+	
+	return 0;
+}
+
+
+/**
+ * camel_imap4_engine_select_folder:
+ * @engine: IMAP4 engine
+ * @folder: folder to select
+ * @ex: exception
+ *
+ * Convenience function to select @folder.
+ *
+ * Returns 0 on success or -1 on fail.
+ **/
+int
+camel_imap4_engine_select_folder (CamelIMAP4Engine *engine, CamelFolder *folder, CamelException *ex)
+{
+	CamelIMAP4RespCode *resp;
+	CamelIMAP4Command *ic;
+	int id, retval = 0;
+	int i;
+	
+	g_return_val_if_fail (CAMEL_IS_IMAP4_ENGINE (engine), -1);
+	g_return_val_if_fail (CAMEL_IS_IMAP4_FOLDER (folder), -1);
+	
+	/* POSSIBLE FIXME: if the folder to be selected will already
+	 * be selected by the time the queue is emptied, simply
+	 * no-op? */
+	
+	ic = camel_imap4_engine_queue (engine, folder, "SELECT %F\r\n", folder);
+	while ((id = camel_imap4_engine_iterate (engine)) < ic->id && id != -1)
+		;
+	
+	if (id == -1 || ic->status != CAMEL_IMAP4_COMMAND_COMPLETE) {
+		camel_exception_xfer (ex, &ic->ex);
+		camel_imap4_command_unref (ic);
+		return -1;
+	}
+	
+	switch (ic->result) {
+	case CAMEL_IMAP4_RESULT_OK:
+		/*folder->mode = 0;*/
+		for (i = 0; i < ic->resp_codes->len; i++) {
+			resp = ic->resp_codes->pdata[i];
+			switch (resp->code) {
+			case CAMEL_IMAP4_RESP_CODE_PERM_FLAGS:
+				folder->permanent_flags = resp->v.flags;
+				break;
+			case CAMEL_IMAP4_RESP_CODE_READONLY:
+				((CamelIMAP4Folder *) folder)->read_only = TRUE;
+				break;
+			case CAMEL_IMAP4_RESP_CODE_READWRITE:
+				((CamelIMAP4Folder *) folder)->read_only = FALSE;
+				break;
+			case CAMEL_IMAP4_RESP_CODE_UIDNEXT:
+				camel_imap4_summary_set_uidnext (folder->summary, resp->v.uidnext);
+				break;
+			case CAMEL_IMAP4_RESP_CODE_UIDVALIDITY:
+				camel_imap4_summary_set_uidvalidity (folder->summary, resp->v.uidvalidity);
+				break;
+			case CAMEL_IMAP4_RESP_CODE_UNSEEN:
+				camel_imap4_summary_set_unseen (folder->summary, resp->v.unseen);
+				break;
+			default:
+				break;
+			}
+		}
+		
+		/*if (folder->mode == 0) {
+		  folder->mode = CAMEL_FOLDER_MODE_READ_ONLY;
+		  g_warning ("Expected to find [READ-ONLY] or [READ-WRITE] in SELECT response");
+		  }*/
+		
+		break;
+	case CAMEL_IMAP4_RESULT_NO:
+		/* FIXME: would be good to save the NO reason into the err message */
+		camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+				      _("Cannot select folder `%s': Invalid mailbox name"),
+				      folder->full_name);
+		retval = -1;
+		break;
+	case CAMEL_IMAP4_RESULT_BAD:
+		camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+				      _("Cannot select folder `%s': Bad command"),
+				      folder->full_name);
+		retval = -1;
+		break;
+	default:
+		g_assert_not_reached ();
+		retval = -1;
+	}
+	
+	camel_imap4_command_unref (ic);
+	
+	return retval;
+}
+
+
+static struct {
+	const char *name;
+	guint32 flag;
+} imap4_capabilities[] = {
+	{ "IMAP4",         CAMEL_IMAP4_CAPABILITY_IMAP4         },
+	{ "IMAP4REV1",     CAMEL_IMAP4_CAPABILITY_IMAP4REV1     },
+	{ "STATUS",        CAMEL_IMAP4_CAPABILITY_STATUS        },
+	{ "NAMESPACE",     CAMEL_IMAP4_CAPABILITY_NAMESPACE     }, /* rfc2342 */
+	{ "UIDPLUS",       CAMEL_IMAP4_CAPABILITY_UIDPLUS       }, /* rfc2359 */
+	{ "LITERAL+",      CAMEL_IMAP4_CAPABILITY_LITERALPLUS   }, /* rfc2088 */
+	{ "LOGINDISABLED", CAMEL_IMAP4_CAPABILITY_LOGINDISABLED },
+	{ "STARTTLS",      CAMEL_IMAP4_CAPABILITY_STARTTLS      }, /* rfc3501 */
+	{ "QUOTA",         CAMEL_IMAP4_CAPABILITY_QUOTA         }, /* rfc2087 */
+	{ "ACL",           CAMEL_IMAP4_CAPABILITY_ACL           }, /* rfc2086 */
+	{ "IDLE",          CAMEL_IMAP4_CAPABILITY_IDLE          }, /* rfc2177 */
+	{ "MULTIAPPEND",   CAMEL_IMAP4_CAPABILITY_MULTIAPPEND   }, /* rfc3502 */
+	{ "UNSELECT",      CAMEL_IMAP4_CAPABILITY_UNSELECT      },
+	{ "XGWEXTENSIONS", CAMEL_IMAP4_CAPABILITY_XGWEXTENSIONS }, /* GroupWise extensions */
+	{ NULL,            0                                    }
+};
+
+static struct {
+	const char *name;
+	guint32 flag;
+} imap4_xgwextensions[] = {
+	{ "XGWMOVE",       CAMEL_IMAP4_CAPABILITY_XGWMOVE       }, /* GroupWise MOVE command */
+	{ NULL,            0                                    }
+};
+
+static int
+parse_xgwextensions (CamelIMAP4Engine *engine, CamelIMAP4Command *ic, guint32 index, camel_imap4_token_t *token, CamelException *ex)
+{
+	int i;
+	
+	if (camel_imap4_engine_next_token (engine, token, ex) == -1)
+		return -1;
+	
+	while (token->token == CAMEL_IMAP4_TOKEN_ATOM) {
+		for (i = 0; imap4_xgwextensions[i].name; i++) {
+			if (!g_ascii_strcasecmp (imap4_xgwextensions[i].name, token->v.atom))
+				engine->capa |= imap4_xgwextensions[i].flag;
+		}
+		
+		if (camel_imap4_engine_next_token (engine, token, ex) == -1)
+			return -1;
+	}
+	
+	if (token->token != '\n') {
+		d(fprintf (stderr, "expected '\\n' at the end of the XGWEXTENSIONS response\n"));
+		goto unexpected;
+	}
+	
+	return 0;
+	
+ unexpected:
+	
+	camel_imap4_utils_set_unexpected_token_error (ex, engine, token);
+	
+	return -1;
+}
+
+static gboolean
+auth_free (gpointer key, gpointer value, gpointer user_data)
+{
+	g_free (key);
+	return TRUE;
+}
+
+static int
+engine_parse_capability (CamelIMAP4Engine *engine, int sentinel, CamelException *ex)
+{
+	camel_imap4_token_t token;
+	int i;
+	
+	/* we assume UTF8 searches work until proven otherwise */
+	engine->capa = CAMEL_IMAP4_CAPABILITY_utf8_search;
+	engine->level = 0;
+	
+	g_hash_table_foreach_remove (engine->authtypes, (GHRFunc) auth_free, NULL);
+	
+	if (camel_imap4_engine_next_token (engine, &token, ex) == -1)
+		return -1;
+	
+	while (token.token == CAMEL_IMAP4_TOKEN_ATOM) {
+		if (!g_ascii_strncasecmp ("AUTH=", token.v.atom, 5)) {
+			CamelServiceAuthType *auth;
+			
+			if ((auth = camel_sasl_authtype (token.v.atom + 5)) != NULL)
+				g_hash_table_insert (engine->authtypes, g_strdup (token.v.atom + 5), auth);
+		} else {
+			for (i = 0; imap4_capabilities[i].name; i++) {
+				if (!g_ascii_strcasecmp (imap4_capabilities[i].name, token.v.atom))
+					engine->capa |= imap4_capabilities[i].flag;
+			}
+		}
+		
+		if (camel_imap4_engine_next_token (engine, &token, ex) == -1)
+			return -1;
+	}
+	
+	if (token.token != sentinel) {
+		camel_imap4_utils_set_unexpected_token_error (ex, engine, &token);
+		return -1;
+	}
+	
+	/* unget our sentinel token */
+	camel_imap4_stream_unget_token (engine->istream, &token);
+	
+	/* figure out which version of IMAP4 we are dealing with */
+	if (engine->capa & CAMEL_IMAP4_CAPABILITY_IMAP4REV1) {
+		engine->level = CAMEL_IMAP4_LEVEL_IMAP4REV1;
+		engine->capa |= CAMEL_IMAP4_CAPABILITY_STATUS;
+	} else if (engine->capa & CAMEL_IMAP4_CAPABILITY_IMAP4) {
+		engine->level = CAMEL_IMAP4_LEVEL_IMAP4;
+	} else {
+		engine->level = CAMEL_IMAP4_LEVEL_UNKNOWN;
+	}
+	
+	return 0;
+}
+
+static int
+engine_parse_flags_list (CamelIMAP4Engine *engine, CamelIMAP4RespCode *resp, int perm, CamelException *ex)
+{
+	guint32 flags = 0;
+	
+	if (camel_imap4_parse_flags_list (engine, &flags, ex) == -1)
+		return-1;
+	
+	if (resp != NULL)
+		resp->v.flags = flags;
+	
+	if (engine->current && engine->current->folder) {
+		if (perm)
+			((CamelFolder *) engine->current->folder)->permanent_flags = flags;
+		/*else
+		  ((CamelFolder *) engine->current->folder)->folder_flags = flags;*/
+	} else if (engine->folder) {
+		if (perm)
+			((CamelFolder *) engine->folder)->permanent_flags = flags;
+		/*else
+		  ((CamelFolder *) engine->folder)->folder_flags = flags;*/
+	} else {
+		fprintf (stderr, "We seem to be in a bit of a pickle. we've just parsed an untagged %s\n"
+			 "response for a folder, yet we do not currently have a folder selected?\n",
+			 perm ? "PERMANENTFLAGS" : "FLAGS");
+	}
+	
+	return 0;
+}
+
+static int
+engine_parse_flags (CamelIMAP4Engine *engine, CamelException *ex)
+{
+	camel_imap4_token_t token;
+	
+	if (engine_parse_flags_list (engine, NULL, FALSE, ex) == -1)
+		return -1;
+	
+	if (camel_imap4_engine_next_token (engine, &token, ex) == -1)
+		return -1;
+	
+	if (token.token != '\n') {
+		d(fprintf (stderr, "Expected to find a '\\n' token after the FLAGS response\n"));
+		camel_imap4_utils_set_unexpected_token_error (ex, engine, &token);
+		return -1;
+	}
+	
+	return 0;
+}
+
+static int
+engine_parse_namespace (CamelIMAP4Engine *engine, CamelException *ex)
+{
+	CamelIMAP4Namespace *namespaces[3], *node, *tail;
+	camel_imap4_token_t token;
+	int i, n = 0;
+	
+	camel_imap4_namespace_clear (&engine->namespaces.personal);
+	camel_imap4_namespace_clear (&engine->namespaces.other);
+	camel_imap4_namespace_clear (&engine->namespaces.shared);
+	
+	if (camel_imap4_engine_next_token (engine, &token, ex) == -1)
+		return -1;
+	
+	do {
+		namespaces[n] = NULL;
+		tail = (CamelIMAP4Namespace *) &namespaces[n];
+		
+		if (token.token == '(') {
+			/* decode the list of namespace pairs */
+			if (camel_imap4_engine_next_token (engine, &token, ex) == -1)
+				goto exception;
+			
+			while (token.token == '(') {
+				/* decode a namespace pair */
+				
+				/* get the path name token */
+				if (camel_imap4_engine_next_token (engine, &token, ex) == -1)
+					goto exception;
+				
+				if (token.token != CAMEL_IMAP4_TOKEN_QSTRING) {
+					d(fprintf (stderr, "Expected to find a qstring token as first element in NAMESPACE pair\n"));
+					camel_imap4_utils_set_unexpected_token_error (ex, engine, &token);
+					goto exception;
+				}
+				
+				node = g_new (CamelIMAP4Namespace, 1);
+				node->next = NULL;
+				node->path = g_strdup (token.v.qstring);
+				
+				/* get the path delimiter token */
+				if (camel_imap4_engine_next_token (engine, &token, ex) == -1) {
+					g_free (node->path);
+					g_free (node);
+					
+					goto exception;
+				}
+				
+				switch (token.token) {
+				case CAMEL_IMAP4_TOKEN_NIL:
+					node->sep = '\0';
+					break;
+				case CAMEL_IMAP4_TOKEN_QSTRING:
+					if (strlen (token.v.qstring) == 1) {
+						node->sep = *token.v.qstring;
+						break;
+					} else {
+						/* check the last char in the path component of the namespace */
+						if (*node->path)
+							node->sep = node->path[strlen (node->path) - 1];
+						else
+							node->sep = '\0';
+					}
+					break;
+				default:
+					d(fprintf (stderr, "Expected to find a nil or a valid qstring token as second element in NAMESPACE pair\n"));
+					camel_imap4_utils_set_unexpected_token_error (ex, engine, &token);
+					g_free (node->path);
+					g_free (node);
+					
+					goto exception;
+				}
+				
+				tail->next = node;
+				tail = node;
+				
+				/* canonicalise the namespace path */
+				if (node->path[strlen (node->path) - 1] == node->sep)
+					node->path[strlen (node->path) - 1] = '\0';
+				
+				/* canonicalise if this is an INBOX namespace */
+				if (!g_ascii_strncasecmp (node->path, "INBOX", 5) &&
+				    (node->path[6] == '\0' || node->path[6] == node->sep))
+					memcpy (node->path, "INBOX", 5);
+				
+				/* get the closing ')' for this namespace pair */
+				if (camel_imap4_engine_next_token (engine, &token, ex) == -1)
+					goto exception;
+				
+				if (token.token != ')') {
+					d(fprintf (stderr, "Expected to find a ')' token to close the current namespace pair\n"));
+					camel_imap4_utils_set_unexpected_token_error (ex, engine, &token);
+					
+					goto exception;
+				}
+				
+				/* get the next token (should be either '(' or ')') */
+				if (camel_imap4_engine_next_token (engine, &token, ex) == -1)
+					goto exception;
+			}
+			
+			if (token.token != ')') {
+				d(fprintf (stderr, "Expected to find a ')' to close the current namespace list\n"));
+				camel_imap4_utils_set_unexpected_token_error (ex, engine, &token);
+				goto exception;
+			}
+		} else if (token.token == CAMEL_IMAP4_TOKEN_NIL) {
+			/* namespace list is NIL */
+			namespaces[n] = NULL;
+		} else {
+			d(fprintf (stderr, "Expected to find either NIL or '(' token in untagged NAMESPACE response\n"));
+			camel_imap4_utils_set_unexpected_token_error (ex, engine, &token);
+			goto exception;
+		}
+		
+		/* get the next token (should be either '(', NIL, or '\n') */
+		if (camel_imap4_engine_next_token (engine, &token, ex) == -1)
+			goto exception;
+		
+		n++;
+	} while (n < 3);
+	
+	engine->namespaces.personal = namespaces[0];
+	engine->namespaces.other = namespaces[1];
+	engine->namespaces.shared = namespaces[2];
+	
+	return 0;
+	
+ exception:
+	
+	for (i = 0; i <= n; i++)
+		camel_imap4_namespace_clear (&namespaces[i]);
+	
+	return -1;
+}
+
+
+/*
+ * resp-text-code  = "ALERT" /
+ *                   "BADCHARSET" [SP "(" astring *(SP astring) ")" ] /
+ *                   capability-data / "PARSE" /
+ *                   "PERMANENTFLAGS" SP "(" [flag-perm *(SP flag-perm)] ")" /
+ *                   "READ-ONLY" / "READ-WRITE" / "TRYCREATE" /
+ *                   "UIDNEXT" SP nz-number / "UIDVALIDITY" SP nz-number /
+ *                   "UNSEEN" SP nz-number /
+ *                   atom [SP 1*<any TEXT-CHAR except "]">]
+ */
+
+static struct {
+	const char *name;
+	camel_imap4_resp_code_t code;
+	int save;
+} imap4_resp_codes[] = {
+	{ "ALERT",          CAMEL_IMAP4_RESP_CODE_ALERT,       0 },
+	{ "BADCHARSET",     CAMEL_IMAP4_RESP_CODE_BADCHARSET,  0 },
+	{ "CAPABILITY",     CAMEL_IMAP4_RESP_CODE_CAPABILITY,  0 },
+	{ "PARSE",          CAMEL_IMAP4_RESP_CODE_PARSE,       1 },
+	{ "PERMANENTFLAGS", CAMEL_IMAP4_RESP_CODE_PERM_FLAGS,  1 },
+	{ "READ-ONLY",      CAMEL_IMAP4_RESP_CODE_READONLY,    1 },
+	{ "READ-WRITE",     CAMEL_IMAP4_RESP_CODE_READWRITE,   1 },
+	{ "TRYCREATE",      CAMEL_IMAP4_RESP_CODE_TRYCREATE,   1 },
+	{ "UIDNEXT",        CAMEL_IMAP4_RESP_CODE_UIDNEXT,     1 },
+	{ "UIDVALIDITY",    CAMEL_IMAP4_RESP_CODE_UIDVALIDITY, 1 },
+	{ "UNSEEN",         CAMEL_IMAP4_RESP_CODE_UNSEEN,      1 },
+	{ "NEWNAME",        CAMEL_IMAP4_RESP_CODE_NEWNAME,     1 },
+	{ "APPENDUID",      CAMEL_IMAP4_RESP_CODE_APPENDUID,   1 },
+	{ "COPYUID",        CAMEL_IMAP4_RESP_CODE_COPYUID,     1 },
+	{ NULL,             CAMEL_IMAP4_RESP_CODE_UNKNOWN,     0 }
+};
+
+
+/**
+ * camel_imap4_engine_parse_resp_code:
+ * @engine: IMAP4 engine
+ * @ex: exception
+ *
+ * Parses a RESP-CODE
+ *
+ * Returns 0 on success or -1 on fail.
+ **/
+int
+camel_imap4_engine_parse_resp_code (CamelIMAP4Engine *engine, CamelException *ex)
+{
+	CamelIMAP4RespCode *resp = NULL;
+	camel_imap4_resp_code_t code;
+	camel_imap4_token_t token;
+	unsigned char *linebuf;
+	size_t len;
+	
+	if (camel_imap4_engine_next_token (engine, &token, ex) == -1)
+		return -1;
+	
+	if (token.token != '[') {
+		d(fprintf (stderr, "Expected a '[' token (followed by a RESP-CODE)\n"));
+		camel_imap4_utils_set_unexpected_token_error (ex, engine, &token);
+		return -1;
+	}
+	
+	if (camel_imap4_engine_next_token (engine, &token, ex) == -1)
+		return -1;
+	
+	if (token.token != CAMEL_IMAP4_TOKEN_ATOM) {
+		d(fprintf (stderr, "Expected an atom token containing a RESP-CODE\n"));
+		camel_imap4_utils_set_unexpected_token_error (ex, engine, &token);
+		return -1;
+	}
+	
+	for (code = 0; imap4_resp_codes[code].name; code++) {
+		if (!strcmp (imap4_resp_codes[code].name, token.v.atom)) {
+			if (engine->current && imap4_resp_codes[code].save) {
+				resp = g_new0 (CamelIMAP4RespCode, 1);
+				resp->code = code;
+			}
+			break;
+		}
+	}
+	
+	switch (code) {
+	case CAMEL_IMAP4_RESP_CODE_ALERT:
+		/* nothing to do here, handled after switch statement */
+		break;
+	case CAMEL_IMAP4_RESP_CODE_BADCHARSET:
+		/* apparently we don't support UTF-8 afterall */
+		engine->capa &= ~CAMEL_IMAP4_CAPABILITY_utf8_search;
+		break;
+	case CAMEL_IMAP4_RESP_CODE_CAPABILITY:
+		/* capability list follows */
+		if (engine_parse_capability (engine, ']', ex) == -1)
+			goto exception;
+		break;
+	case CAMEL_IMAP4_RESP_CODE_PERM_FLAGS:
+		/* flag list follows */
+		if (engine_parse_flags_list (engine, resp, TRUE, ex) == -1)
+			goto exception;
+		break;
+	case CAMEL_IMAP4_RESP_CODE_READONLY:
+		break;
+	case CAMEL_IMAP4_RESP_CODE_READWRITE:
+		break;
+	case CAMEL_IMAP4_RESP_CODE_TRYCREATE:
+		break;
+	case CAMEL_IMAP4_RESP_CODE_UIDNEXT:
+		if (camel_imap4_engine_next_token (engine, &token, ex) == -1)
+			goto exception;
+		
+		if (token.token != CAMEL_IMAP4_TOKEN_NUMBER) {
+			d(fprintf (stderr, "Expected an nz_number token as an argument to the UIDNEXT RESP-CODE\n"));
+			camel_imap4_utils_set_unexpected_token_error (ex, engine, &token);
+			goto exception;
+		}
+		
+		if (resp != NULL)
+			resp->v.uidnext = token.v.number;
+		
+		break;
+	case CAMEL_IMAP4_RESP_CODE_UIDVALIDITY:
+		if (camel_imap4_engine_next_token (engine, &token, ex) == -1)
+			goto exception;
+		
+		if (token.token != CAMEL_IMAP4_TOKEN_NUMBER) {
+			d(fprintf (stderr, "Expected an nz_number token as an argument to the UIDVALIDITY RESP-CODE\n"));
+			camel_imap4_utils_set_unexpected_token_error (ex, engine, &token);
+			goto exception;
+		}
+		
+		if (resp != NULL)
+			resp->v.uidvalidity = token.v.number;
+		
+		break;
+	case CAMEL_IMAP4_RESP_CODE_UNSEEN:
+		if (camel_imap4_engine_next_token (engine, &token, ex) == -1)
+			return -1;
+		
+		if (token.token != CAMEL_IMAP4_TOKEN_NUMBER) {
+			d(fprintf (stderr, "Expected an nz_number token as an argument to the UNSEEN RESP-CODE\n"));
+			camel_imap4_utils_set_unexpected_token_error (ex, engine, &token);
+			goto exception;
+		}
+		
+		if (resp != NULL)
+			resp->v.unseen = token.v.number;
+		
+		break;
+	case CAMEL_IMAP4_RESP_CODE_NEWNAME:
+		/* this RESP-CODE may actually be removed - see here:
+		 * http://www.washington.edu/imap4/listarch/2001/msg00058.html */
+		
+		if (camel_imap4_engine_next_token (engine, &token, ex) == -1)
+			return -1;
+		
+		if (token.token != CAMEL_IMAP4_TOKEN_ATOM && token.token != CAMEL_IMAP4_TOKEN_QSTRING) {
+			d(fprintf (stderr, "Expected an atom or qstring token as the first argument to the NEWNAME RESP-CODE\n"));
+			camel_imap4_utils_set_unexpected_token_error (ex, engine, &token);
+			goto exception;
+		}
+		
+		if (resp != NULL)
+			resp->v.newname[0] = g_strdup (token.v.atom);
+		
+		if (token.token != CAMEL_IMAP4_TOKEN_ATOM && token.token != CAMEL_IMAP4_TOKEN_QSTRING) {
+			d(fprintf (stderr, "Expected an atom or qstring token as the second argument to the NEWNAME RESP-CODE\n"));
+			camel_imap4_utils_set_unexpected_token_error (ex, engine, &token);
+			goto exception;
+		}
+		
+		if (resp != NULL)
+			resp->v.newname[1] = g_strdup (token.v.atom);
+		
+		break;
+	case CAMEL_IMAP4_RESP_CODE_APPENDUID:
+		if (camel_imap4_engine_next_token (engine, &token, ex) == -1)
+			return -1;
+		
+		if (token.token != CAMEL_IMAP4_TOKEN_NUMBER) {
+			d(fprintf (stderr, "Expected an nz_number token as the first argument to the APPENDUID RESP-CODE\n"));
+			camel_imap4_utils_set_unexpected_token_error (ex, engine, &token);
+			goto exception;
+		}
+		
+		if (resp != NULL)
+			resp->v.appenduid.uidvalidity = token.v.number;
+		
+		if (camel_imap4_engine_next_token (engine, &token, ex) == -1)
+			return -1;
+		
+		if (token.token != CAMEL_IMAP4_TOKEN_NUMBER) {
+			d(fprintf (stderr, "Expected an nz_number token as the second argument to the APPENDUID RESP-CODE\n"));
+			camel_imap4_utils_set_unexpected_token_error (ex, engine, &token);
+			goto exception;
+		}
+		
+		if (resp != NULL)
+			resp->v.appenduid.uid = token.v.number;
+		
+		break;
+	case CAMEL_IMAP4_RESP_CODE_COPYUID:
+		if (camel_imap4_engine_next_token (engine, &token, ex) == -1)
+			return -1;
+		
+		if (token.token != CAMEL_IMAP4_TOKEN_NUMBER) {
+			d(fprintf (stderr, "Expected an nz_number token as the first argument to the COPYUID RESP-CODE\n"));
+			camel_imap4_utils_set_unexpected_token_error (ex, engine, &token);
+			goto exception;
+		}
+		
+		if (resp != NULL)
+			resp->v.copyuid.uidvalidity = token.v.number;
+		
+		if (camel_imap4_engine_next_token (engine, &token, ex) == -1)
+			return -1;
+		
+		if (token.token != CAMEL_IMAP4_TOKEN_ATOM && token.token != CAMEL_IMAP4_TOKEN_NUMBER) {
+			d(fprintf (stderr, "Expected an atom or numeric token as the second argument to the COPYUID RESP-CODE\n"));
+			camel_imap4_utils_set_unexpected_token_error (ex, engine, &token);
+			goto exception;
+		}
+		
+		if (resp != NULL) {
+			if (token.token == CAMEL_IMAP4_TOKEN_NUMBER)
+				resp->v.copyuid.srcset = g_strdup_printf ("%u", token.v.number);
+			else
+				resp->v.copyuid.srcset = g_strdup (token.v.atom);
+		}
+		
+		if (camel_imap4_engine_next_token (engine, &token, ex) == -1)
+			return -1;
+		
+		if (token.token != CAMEL_IMAP4_TOKEN_ATOM && token.token != CAMEL_IMAP4_TOKEN_NUMBER) {
+			d(fprintf (stderr, "Expected an atom or numeric token as the third argument to the APPENDUID RESP-CODE\n"));
+			camel_imap4_utils_set_unexpected_token_error (ex, engine, &token);
+			goto exception;
+		}
+		
+		if (resp != NULL) {
+			if (token.token == CAMEL_IMAP4_TOKEN_NUMBER)
+				resp->v.copyuid.destset = g_strdup_printf ("%u", token.v.number);
+			else
+				resp->v.copyuid.destset = g_strdup (token.v.atom);
+		}
+		
+		break;
+	default:
+		d(fprintf (stderr, "Unknown RESP-CODE encountered: %s\n", token.v.atom));
+		
+		/* extensions are of the form: "[" atom [SPACE 1*<any TEXT_CHAR except "]">] "]" */
+		
+		/* eat up the TEXT_CHARs */
+		while (token.token != ']' && token.token != '\n') {
+			if (camel_imap4_engine_next_token (engine, &token, ex) == -1)
+				goto exception;
+		}
+		
+		break;
+	}
+	
+	while (token.token != ']' && token.token != '\n') {
+		if (camel_imap4_engine_next_token (engine, &token, ex) == -1)
+			goto exception;
+	}
+	
+	if (token.token != ']') {
+		camel_imap4_utils_set_unexpected_token_error (ex, engine, &token);
+		d(fprintf (stderr, "Expected to find a ']' token after the RESP-CODE\n"));
+		return -1;
+	}
+	
+	if (code == CAMEL_IMAP4_RESP_CODE_ALERT) {
+		if (camel_imap4_engine_line (engine, &linebuf, &len, ex) == -1)
+			goto exception;
+		
+		camel_session_alert_user (engine->session, CAMEL_SESSION_ALERT_INFO, linebuf, FALSE);
+		g_free (linebuf);
+	} else if (resp != NULL && code == CAMEL_IMAP4_RESP_CODE_PARSE) {
+		if (camel_imap4_engine_line (engine, &linebuf, &len, ex) == -1)
+			goto exception;
+		
+		resp->v.parse = linebuf;
+	} else {
+		/* eat up the rest of the response */
+		if (camel_imap4_engine_line (engine, NULL, NULL, ex) == -1)
+			goto exception;
+	}
+	
+	if (resp != NULL)
+		g_ptr_array_add (engine->current->resp_codes, resp);
+	
+	return 0;
+	
+ exception:
+	
+	if (resp != NULL)
+		camel_imap4_resp_code_free (resp);
+	
+	return -1;
+}
+
+
+/**
+ * camel_imap4_engine_handle_untagged_1:
+ * @engine: IMAP4 engine
+ * @token: IMAP4 token
+ * @ex: exception
+ *
+ * Handles a single untagged response
+ *
+ * Returns -1 on error or one of
+ * CAMEL_IMAP4_UNTAGGED_[OK,NO,BAD,PREAUTH,HANDLED] on success
+ **/
+int
+camel_imap4_engine_handle_untagged_1 (CamelIMAP4Engine *engine, camel_imap4_token_t *token, CamelException *ex)
+{
+	int code = CAMEL_IMAP4_UNTAGGED_HANDLED;
+	CamelIMAP4Command *ic = engine->current;
+	CamelIMAP4UntaggedCallback untagged;
+	CamelFolder *folder;
+	unsigned int v;
+	
+	if (camel_imap4_engine_next_token (engine, token, ex) == -1)
+		return -1;
+	
+	if (token->token == CAMEL_IMAP4_TOKEN_ATOM) {
+		if (!strcmp ("BYE", token->v.atom)) {
+			/* we don't care if we fail here, either way we've been disconnected */
+			if (camel_imap4_engine_next_token (engine, token, NULL) == 0) {
+				if (token->token == '[') {
+					camel_imap4_stream_unget_token (engine->istream, token);
+					camel_imap4_engine_parse_resp_code (engine, NULL);
+				} else {
+					camel_imap4_engine_line (engine, NULL, NULL, NULL);
+				}
+			}
+			
+			engine->state = CAMEL_IMAP4_ENGINE_DISCONNECTED;
+			
+			/* we don't return -1 here because there may be more untagged responses after the BYE */
+		} else if (!strcmp ("CAPABILITY", token->v.atom)) {
+			/* capability tokens follow */
+			if (engine_parse_capability (engine, '\n', ex) == -1)
+				return -1;
+			
+			/* find the eoln token */
+			if (camel_imap4_engine_next_token (engine, token, ex) == -1)
+				return -1;
+			
+			if (token->token != '\n') {
+				camel_imap4_utils_set_unexpected_token_error (ex, engine, token);
+				return -1;
+			}
+		} else if (!strcmp ("FLAGS", token->v.atom)) {
+			/* flags list follows */
+			if (engine_parse_flags (engine, ex) == -1)
+				return -1;
+		} else if (!strcmp ("NAMESPACE", token->v.atom)) {
+			if (engine_parse_namespace (engine, ex) == -1)
+				return -1;
+		} else if (!strcmp ("NO", token->v.atom) || !strcmp ("BAD", token->v.atom)) {
+			code = !strcmp ("NO", token->v.atom) ? CAMEL_IMAP4_UNTAGGED_NO : CAMEL_IMAP4_UNTAGGED_BAD;
+			
+			/* our command has been rejected */
+			if (camel_imap4_engine_next_token (engine, token, ex) == -1)
+				return -1;
+			
+			if (token->token == '[') {
+				/* we have a resp code */
+				camel_imap4_stream_unget_token (engine->istream, token);
+				if (camel_imap4_engine_parse_resp_code (engine, ex) == -1)
+					return -1;
+			} else if (token->token != '\n') {
+				/* we just have resp text */
+				if (camel_imap4_engine_line (engine, NULL, NULL, ex) == -1)
+					return -1;
+			}
+		} else if (!strcmp ("OK", token->v.atom)) {
+			code = CAMEL_IMAP4_UNTAGGED_OK;
+			
+			if (engine->state == CAMEL_IMAP4_ENGINE_CONNECTED) {
+				/* initial server greeting */
+				engine->state = CAMEL_IMAP4_ENGINE_PREAUTH;
+			}
+			
+			if (camel_imap4_engine_next_token (engine, token, ex) == -1)
+				return -1;
+			
+			if (token->token == '[') {
+				/* we have a resp code */
+				camel_imap4_stream_unget_token (engine->istream, token);
+				if (camel_imap4_engine_parse_resp_code (engine, ex) == -1)
+					return -1;
+			} else {
+				/* we just have resp text */
+				if (camel_imap4_engine_line (engine, NULL, NULL, ex) == -1)
+					return -1;
+			}
+		} else if (!strcmp ("PREAUTH", token->v.atom)) {
+			code = CAMEL_IMAP4_UNTAGGED_PREAUTH;
+			
+			if (engine->state == CAMEL_IMAP4_ENGINE_CONNECTED)
+				engine->state = CAMEL_IMAP4_ENGINE_AUTHENTICATED;
+			
+			if (camel_imap4_engine_parse_resp_code (engine, ex) == -1)
+				return -1;
+		} else if (ic && (untagged = g_hash_table_lookup (ic->untagged, token->v.atom))) {
+			/* registered untagged handler for imap4 command */
+			if (untagged (engine, ic, 0, token, ex) == -1)
+				return -1;
+		} else {
+			d(fprintf (stderr, "Unhandled atom token in untagged response: %s", token->v.atom));
+			
+			if (camel_imap4_engine_eat_line (engine, ex) == -1)
+				return -1;
+		}
+	} else if (token->token == CAMEL_IMAP4_TOKEN_NUMBER) {
+		/* we probably have something like "* 1 EXISTS" */
+		v = token->v.number;
+		
+		if (camel_imap4_engine_next_token (engine, token, ex) == -1)
+			return -1;
+		
+		if (token->token != CAMEL_IMAP4_TOKEN_ATOM) {
+			camel_imap4_utils_set_unexpected_token_error (ex, engine, token);
+			
+			return -1;
+		}
+		
+		/* which folder is this EXISTS/EXPUNGE/RECENT acting on? */
+		if (engine->current && engine->current->folder)
+			folder = (CamelFolder *) engine->current->folder;
+		else if (engine->folder)
+			folder = (CamelFolder *) engine->folder;
+		else
+			folder = NULL;
+		
+		/* NOTE: these can be over-ridden by a registered untagged response handler */
+		if (!strcmp ("EXISTS", token->v.atom)) {
+			camel_imap4_summary_set_exists (folder->summary, v);
+		} else if (!strcmp ("EXPUNGE", token->v.atom) || !strcmp ("XGWMOVE", token->v.atom)) {
+			camel_imap4_summary_expunge (folder->summary, (int) v);
+		} else if (!strcmp ("RECENT", token->v.atom)) {
+			camel_imap4_summary_set_recent (folder->summary, v);
+		} else if (ic && (untagged = g_hash_table_lookup (ic->untagged, token->v.atom))) {
+			/* registered untagged handler for imap4 command */
+			if (untagged (engine, ic, v, token, ex) == -1)
+				return -1;
+		} else {
+			d(fprintf (stderr, "Unrecognized untagged response: * %u %s\n", v, token->v.atom));
+		}
+		
+		/* find the eoln token */
+		if (camel_imap4_engine_eat_line (engine, ex) == -1)
+			return -1;
+	} else {
+		camel_imap4_utils_set_unexpected_token_error (ex, engine, token);
+		
+		return -1;
+	}
+	
+	return code;
+}
+
+
+/**
+ * camel_imap4_engine_handle_untagged:
+ * @engine: IMAP4 engine
+ * @ex: exception
+ *
+ * Handle a stream of untagged responses.
+ **/
+void
+camel_imap4_engine_handle_untagged (CamelIMAP4Engine *engine, CamelException *ex)
+{
+	camel_imap4_token_t token;
+	
+	g_return_if_fail (CAMEL_IS_IMAP4_ENGINE (engine));
+	
+	do {
+		if (camel_imap4_engine_next_token (engine, &token, ex) == -1)
+			goto exception;
+		
+		if (token.token != '*')
+			break;
+		
+		if (camel_imap4_engine_handle_untagged_1 (engine, &token, ex) == -1)
+			goto exception;
+	} while (1);
+	
+	camel_imap4_stream_unget_token (engine->istream, &token);
+	
+	return;
+	
+ exception:
+	
+	engine->state = CAMEL_IMAP4_ENGINE_DISCONNECTED;
+}
+
+
+static int
+imap4_process_command (CamelIMAP4Engine *engine, CamelIMAP4Command *ic)
+{
+	int retval;
+	
+	while ((retval = camel_imap4_command_step (ic)) == 0)
+		;
+	
+	if (retval == -1) {
+		engine->state = CAMEL_IMAP4_ENGINE_DISCONNECTED;
+		return -1;
+	}
+	
+	return 0;
+}
+
+
+static void
+engine_prequeue_folder_select (CamelIMAP4Engine *engine)
+{
+	CamelIMAP4Command *ic;
+	const char *cmd;
+	
+	ic = (CamelIMAP4Command *) engine->queue.head;
+	cmd = (const char *) ic->parts->buffer;
+	
+	if (!ic->folder || ic->folder == engine->folder ||
+	    !strncmp (cmd, "SELECT ", 7) || !strncmp (cmd, "EXAMINE ", 8)) {
+		/* no need to pre-queue a SELECT */
+		return;
+	}
+	
+	/* we need to pre-queue a SELECT */
+	ic = camel_imap4_engine_prequeue (engine, (CamelFolder *) ic->folder, "SELECT %F\r\n", ic->folder);
+	ic->user_data = engine;
+	
+	camel_imap4_command_unref (ic);
+}
+
+
+static int
+engine_state_change (CamelIMAP4Engine *engine, CamelIMAP4Command *ic)
+{
+	const char *cmd;
+	int retval = 0;
+	
+	cmd = ic->parts->buffer;
+	if (!strncmp (cmd, "SELECT ", 7) || !strncmp (cmd, "EXAMINE ", 8)) {
+		if (ic->result == CAMEL_IMAP4_RESULT_OK) {
+			/* Update the selected folder */
+			camel_object_ref (ic->folder);
+			if (engine->folder)
+				camel_object_unref (engine->folder);
+			engine->folder = ic->folder;
+			
+			engine->state = CAMEL_IMAP4_ENGINE_SELECTED;
+		} else if (ic->user_data == engine) {
+			/* the engine pre-queued this SELECT command */
+			retval = -1;
+		}
+	} else if (!strncmp (cmd, "CLOSE", 5)) {
+		if (ic->result == CAMEL_IMAP4_RESULT_OK)
+			engine->state = CAMEL_IMAP4_ENGINE_AUTHENTICATED;
+	} else if (!strncmp (cmd, "LOGOUT", 6)) {
+		engine->state = CAMEL_IMAP4_ENGINE_DISCONNECTED;
+	}
+	
+	return retval;
+}
+
+
+/**
+ * camel_imap4_engine_iterate:
+ * @engine: IMAP4 engine
+ *
+ * Processes the first command in the queue.
+ *
+ * Returns the id of the processed command, %0 if there were no
+ * commands to process, or %-1 on error.
+ *
+ * Note: more details on the error will be held on the
+ * #CamelIMAP4Command that failed.
+ **/
+int
+camel_imap4_engine_iterate (CamelIMAP4Engine *engine)
+{
+	CamelIMAP4Command *ic, *nic;
+	GPtrArray *resp_codes;
+	int retries = 0;
+	int retval;
+	
+	if (e_dlist_empty (&engine->queue))
+		return 0;
+	
+ retry:
+	/* FIXME: it would be nicer if we didn't have to check the stream's disconnected status */
+	if ((engine->state == CAMEL_IMAP4_ENGINE_DISCONNECTED || engine->istream->disconnected)
+	    && !engine->reconnecting) {
+		CamelException rex;
+		gboolean connected;
+		
+		camel_exception_init (&rex);
+		engine->reconnecting = TRUE;
+		connected = engine->reconnect (engine, &rex);
+		engine->reconnecting = FALSE;
+		
+		if (!connected) {
+			/* pop the first command and act as tho it failed (which, technically, it did...) */
+			ic = (CamelIMAP4Command *) e_dlist_remhead (&engine->queue);
+			ic->status = CAMEL_IMAP4_COMMAND_ERROR;
+			camel_exception_xfer (&ic->ex, &rex);
+			camel_imap4_command_unref (ic);
+			return -1;
+		}
+	}
+	
+	/* check to see if we need to pre-queue a SELECT, if so do it */
+	engine_prequeue_folder_select (engine);
+	
+	engine->current = ic = (CamelIMAP4Command *) e_dlist_remhead (&engine->queue);
+	ic->status = CAMEL_IMAP4_COMMAND_ACTIVE;
+	
+	if ((retval = imap4_process_command (engine, ic)) != -1) {
+		if (engine_state_change (engine, ic) == -1) {
+			/* This can ONLY happen if @ic was the pre-queued SELECT command
+			 * and it got a NO or BAD response.
+			 *
+			 * We have to pop the next imap4 command or we'll get into an
+			 * infinite loop. In order to provide @nic's owner with as much
+			 * information as possible, we move all @ic status information
+			 * over to @nic and pretend we just processed @nic.
+			 **/
+			
+			nic = (CamelIMAP4Command *) e_dlist_remhead (&engine->queue);
+			
+			nic->status = ic->status;
+			nic->result = ic->result;
+			resp_codes = nic->resp_codes;
+			nic->resp_codes = ic->resp_codes;
+			ic->resp_codes = resp_codes;
+			
+			camel_exception_xfer (&nic->ex, &ic->ex);
+			
+			camel_imap4_command_unref (ic);
+			ic = nic;
+		}
+		
+		retval = ic->id;
+	} else if (!engine->reconnecting && retries < 3) {
+		/* put @ic back in the queue and retry */
+		e_dlist_addhead (&engine->queue, (EDListNode *) ic);
+		camel_imap4_command_reset (ic);
+		retries++;
+		goto retry;
+	} else {
+		camel_exception_setv (&ic->ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
+				      _("Failed to send command to IMAP server %s: %s"),
+				      engine->url->host, errno ? g_strerror (errno) :
+				      _("service unavailable"));
+	}
+	
+	camel_imap4_command_unref (ic);
+	
+	return retval;
+}
+
+
+/**
+ * camel_imap4_engine_queue:
+ * @engine: IMAP4 engine
+ * @folder: IMAP4 folder that the command will affect (or %NULL if it doesn't matter)
+ * @format: command format
+ * @Varargs: arguments
+ *
+ * Basically the same as camel_imap4_command_new() except that this
+ * function also places the command in the engine queue.
+ *
+ * Returns the CamelIMAP4Command.
+ **/
+CamelIMAP4Command *
+camel_imap4_engine_queue (CamelIMAP4Engine *engine, CamelFolder *folder, const char *format, ...)
+{
+	CamelIMAP4Command *ic;
+	va_list args;
+	
+	g_return_val_if_fail (CAMEL_IS_IMAP4_ENGINE (engine), NULL);
+	
+	va_start (args, format);
+	ic = camel_imap4_command_newv (engine, (CamelIMAP4Folder *) folder, format, args);
+	va_end (args);
+	
+	ic->id = engine->nextid++;
+	e_dlist_addtail (&engine->queue, (EDListNode *) ic);
+	camel_imap4_command_ref (ic);
+	
+	return ic;
+}
+
+
+/**
+ * camel_imap4_engine_prequeue:
+ * @engine: IMAP4 engine
+ * @folder: IMAP4 folder that the command will affect (or %NULL if it doesn't matter)
+ * @format: command format
+ * @Varargs: arguments
+ *
+ * Same as camel_imap4_engine_queue() except this places the new
+ * command at the head of the queue.
+ *
+ * Returns the CamelIMAP4Command.
+ **/
+CamelIMAP4Command *
+camel_imap4_engine_prequeue (CamelIMAP4Engine *engine, CamelFolder *folder, const char *format, ...)
+{
+	CamelIMAP4Command *ic;
+	va_list args;
+	
+	g_return_val_if_fail (CAMEL_IS_IMAP4_ENGINE (engine), NULL);
+	
+	va_start (args, format);
+	ic = camel_imap4_command_newv (engine, (CamelIMAP4Folder *) folder, format, args);
+	va_end (args);
+	
+	if (e_dlist_empty (&engine->queue)) {
+		e_dlist_addtail (&engine->queue, (EDListNode *) ic);
+		ic->id = engine->nextid++;
+	} else {
+		CamelIMAP4Command *nic;
+		EDListNode *node;
+		
+		node = (EDListNode *) ic;
+		e_dlist_addhead (&engine->queue, node);
+		nic = (CamelIMAP4Command *) node->next;
+		ic->id = nic->id - 1;
+		
+		if (ic->id == 0) {
+			/* increment all command ids */
+			node = engine->queue.head;
+			while (node->next) {
+				nic = (CamelIMAP4Command *) node;
+				node = node->next;
+				nic->id++;
+			}
+		}
+	}
+	
+	camel_imap4_command_ref (ic);
+	
+	return ic;
+}
+
+
+/**
+ * camel_imap4_engine_dequeue:
+ * @engine: IMAP4 engine
+ * @ic: IMAP4 command
+ *
+ * Removes @ic from the processing queue.
+ **/
+void
+camel_imap4_engine_dequeue (CamelIMAP4Engine *engine, CamelIMAP4Command *ic)
+{
+	EDListNode *node = (EDListNode *) ic;
+	
+	if (node->next == NULL && node->prev == NULL)
+		return;
+	
+	e_dlist_remove (node);
+	node->next = NULL;
+	node->prev = NULL;
+	
+	camel_imap4_command_unref (ic);
+}
+
+
+/**
+ * camel_imap4_engine_next_token:
+ * @engine: IMAP4 engine
+ * @token: IMAP4 token
+ * @ex: exception
+ *
+ * Wraps camel_imap4_stream_next_token() to set an exception on
+ * failure and updates the engine state to DISCONNECTED if the stream
+ * gets disconencted.
+ *
+ * Returns 0 on success or -1 on fail.
+ **/
+int
+camel_imap4_engine_next_token (CamelIMAP4Engine *engine, camel_imap4_token_t *token, CamelException *ex)
+{
+	if (camel_imap4_stream_next_token (engine->istream, token) == -1) {
+		camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+				      _("IMAP4 server %s unexpectedly disconnected: %s"),
+				      engine->url->host, errno ? g_strerror (errno) : _("Unknown"));
+		
+		engine->state = CAMEL_IMAP4_ENGINE_DISCONNECTED;
+		
+		return -1;
+	}
+	
+	return 0;
+}
+
+
+/**
+ * camel_imap4_engine_eat_line:
+ * @engine: IMAP4 engine
+ * @ex: exception
+ *
+ * Gobbles up the remainder of the response line.
+ *
+ * Returns 0 on success or -1 on fail
+ **/
+int
+camel_imap4_engine_eat_line (CamelIMAP4Engine *engine, CamelException *ex)
+{
+	camel_imap4_token_t token;
+	unsigned char *literal;
+	int retval;
+	size_t n;
+	
+	do {
+		if (camel_imap4_engine_next_token (engine, &token, ex) == -1)
+			return -1;
+		
+		if (token.token == CAMEL_IMAP4_TOKEN_LITERAL) {
+			while ((retval = camel_imap4_stream_literal (engine->istream, &literal, &n)) == 1)
+				;
+			
+			if (retval == -1) {
+				camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+						      _("IMAP4 server %s unexpectedly disconnected: %s"),
+						      engine->url->host, errno ? g_strerror (errno) : _("Unknown"));
+				
+				engine->state = CAMEL_IMAP4_ENGINE_DISCONNECTED;
+				
+				return -1;
+			}
+		}
+	} while (token.token != '\n');
+	
+	return 0;
+}
+
+
+/**
+ * camel_imap4_engine_line:
+ * @engine: IMAP4 engine
+ * @line: line pointer
+ * @len: length pointer
+ * @ex: exception
+ *
+ * Reads in a single line of input from the IMAP4 server and updates
+ * @line to point to the line buffer. @len is set to the length of the
+ * line buffer. @line must be free'd using g_free().
+ *
+ * Returns 0 on success or -1 on fail
+ **/
+int
+camel_imap4_engine_line (CamelIMAP4Engine *engine, unsigned char **line, size_t *len, CamelException *ex)
+{
+	GByteArray *linebuf = NULL;
+	unsigned char *buf;
+	size_t buflen;
+	int retval;
+	
+	if (line != NULL)
+		linebuf = g_byte_array_new ();
+	
+	while ((retval = camel_imap4_stream_line (engine->istream, &buf, &buflen)) > 0) {
+		if (linebuf != NULL)
+			g_byte_array_append (linebuf, buf, buflen);
+	}
+	
+	if (retval == -1) {
+		camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+				      _("IMAP4 server %s unexpectedly disconnected: %s"),
+				      engine->url->host, errno ? g_strerror (errno) : _("Unknown"));
+		
+		if (linebuf != NULL)
+			g_byte_array_free (linebuf, TRUE);
+		
+		engine->state = CAMEL_IMAP4_ENGINE_DISCONNECTED;
+		
+		return -1;
+	}
+	
+	if (linebuf != NULL) {
+		g_byte_array_append (linebuf, buf, buflen);
+		
+		*line = linebuf->data;
+		*len = linebuf->len;
+		
+		g_byte_array_free (linebuf, FALSE);
+	}
+	
+	return 0;
+}
+
+
+/**
+ * camel_imap4_engine_literal:
+ * @engine: IMAP4 engine
+ * @literal: literal pointer
+ * @len: len pointer
+ * @ex: exception
+ *
+ * Reads in an entire literal string and updates @literal to point to
+ * it. @len is set to the length of the literal. @literal will also
+ * conveniently be terminated with a nul-byte. @literal must be free'd
+ * using g_free().
+ *
+ * Returns 0 on success or -1 on fail.
+ **/
+int
+camel_imap4_engine_literal (CamelIMAP4Engine *engine, unsigned char **literal, size_t *len, CamelException *ex)
+{
+	GByteArray *literalbuf = NULL;
+	unsigned char *buf;
+	size_t buflen;
+	int retval;
+	
+	if (literal != NULL)
+		literalbuf = g_byte_array_new ();
+	
+	while ((retval = camel_imap4_stream_literal (engine->istream, &buf, &buflen)) > 0) {
+		if (literalbuf != NULL)
+			g_byte_array_append (literalbuf, buf, buflen);
+	}
+	
+	if (retval == -1) {
+		camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+				      _("IMAP4 server %s unexpectedly disconnected: %s"),
+				      engine->url->host, errno ? g_strerror (errno) : _("Unknown"));
+		
+		if (literalbuf != NULL)
+			g_byte_array_free (literalbuf, TRUE);
+		
+		engine->state = CAMEL_IMAP4_ENGINE_DISCONNECTED;
+		
+		return -1;
+	}
+	
+	if (literalbuf != NULL) {
+		g_byte_array_append (literalbuf, buf, buflen);
+		g_byte_array_append (literalbuf, "", 1);
+		
+		*literal = literalbuf->data;
+		*len = literalbuf->len - 1;
+		
+		g_byte_array_free (literalbuf, FALSE);
+	}
+	
+	return 0;
+}
+
+
+/**
+ * camel_imap4_engine_nstring:
+ * @engine: IMAP4 engine
+ * @nstring: nstring pointer
+ * @ex: exception
+ *
+ * Reads in an nstring (NIL, atom, qstring or literal) and updates
+ * @nstring to point to it. @nstring must be free'd using g_free().
+ *
+ * Returns 0 on success or -1 on fail.
+ **/
+int
+camel_imap4_engine_nstring (CamelIMAP4Engine *engine, unsigned char **nstring, CamelException *ex)
+{
+	camel_imap4_token_t token;
+	size_t n;
+	
+	if (camel_imap4_engine_next_token (engine, &token, ex) == -1)
+		return -1;
+	
+	switch (token.token) {
+	case CAMEL_IMAP4_TOKEN_NIL:
+		*nstring = NULL;
+		break;
+	case CAMEL_IMAP4_TOKEN_ATOM:
+		*nstring = g_strdup (token.v.atom);
+		break;
+	case CAMEL_IMAP4_TOKEN_QSTRING:
+		*nstring = g_strdup (token.v.qstring);
+		break;
+	case CAMEL_IMAP4_TOKEN_LITERAL:
+		if (camel_imap4_engine_literal (engine, nstring, &n, ex) == -1)
+			return -1;
+		break;
+	default:
+		camel_imap4_utils_set_unexpected_token_error (ex, engine, &token);
+		return -1;
+	}
+	
+	return 0;
+}
+
+
+/**
+ * camel_imap4_resp_code_free:
+ * @rcode: RESP-CODE
+ *
+ * Free's the RESP-CODE
+ **/
+void
+camel_imap4_resp_code_free (CamelIMAP4RespCode *rcode)
+{
+	switch (rcode->code) {
+	case CAMEL_IMAP4_RESP_CODE_PARSE:
+		g_free (rcode->v.parse);
+		break;
+	case CAMEL_IMAP4_RESP_CODE_NEWNAME:
+		g_free (rcode->v.newname[0]);
+		g_free (rcode->v.newname[1]);
+		break;
+	case CAMEL_IMAP4_RESP_CODE_COPYUID:
+		g_free (rcode->v.copyuid.srcset);
+		g_free (rcode->v.copyuid.destset);
+		break;
+	default:
+		break;
+	}
+	
+	g_free (rcode);
+}

Added: trunk/imap4/camel-imap4-engine.h
==============================================================================
--- (empty file)
+++ trunk/imap4/camel-imap4-engine.h	Sat May 10 17:53:05 2008
@@ -0,0 +1,236 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*  Camel
+ *  Copyright (C) 1999-2007 Novell, Inc. (www.novell.com)
+ *
+ *  Authors: Jeffrey Stedfast <fejj novell com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU Lesser General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  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 Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+
+#ifndef __CAMEL_IMAP4_ENGINE_H__
+#define __CAMEL_IMAP4_ENGINE_H__
+
+#include <stdarg.h>
+
+#include <glib.h>
+
+#include <libedataserver/e-msgport.h>
+
+#include <camel/camel-stream.h>
+#include <camel/camel-folder.h>
+#include <camel/camel-session.h>
+
+#define CAMEL_TYPE_IMAP4_ENGINE            (camel_imap4_engine_get_type ())
+#define CAMEL_IMAP4_ENGINE(obj)            (CAMEL_CHECK_CAST ((obj), CAMEL_TYPE_IMAP4_ENGINE, CamelIMAP4Engine))
+#define CAMEL_IMAP4_ENGINE_CLASS(klass)    (CAMEL_CHECK_CLASS_CAST ((klass), CAMEL_TYPE_IMAP4_ENGINE, CamelIMAP4EngineClass))
+#define CAMEL_IS_IMAP4_ENGINE(obj)         (CAMEL_CHECK_TYPE ((obj), CAMEL_TYPE_IMAP4_ENGINE))
+#define CAMEL_IS_IMAP4_ENGINE_CLASS(klass) (CAMEL_CHECK_CLASS_TYPE ((klass), CAMEL_TYPE_IMAP4_ENGINE))
+#define CAMEL_IMAP4_ENGINE_GET_CLASS(obj)  (CAMEL_CHECK_GET_CLASS ((obj), CAMEL_TYPE_IMAP4_ENGINE, CamelIMAP4EngineClass))
+
+G_BEGIN_DECLS
+
+typedef struct _CamelIMAP4Engine CamelIMAP4Engine;
+typedef struct _CamelIMAP4EngineClass CamelIMAP4EngineClass;
+
+struct _camel_imap4_token_t;
+struct _CamelIMAP4Command;
+struct _CamelIMAP4Folder;
+struct _CamelIMAP4Stream;
+
+typedef enum {
+	CAMEL_IMAP4_ENGINE_DISCONNECTED,
+	CAMEL_IMAP4_ENGINE_CONNECTED,
+	CAMEL_IMAP4_ENGINE_PREAUTH,
+	CAMEL_IMAP4_ENGINE_AUTHENTICATED,
+	CAMEL_IMAP4_ENGINE_SELECTED,
+} camel_imap4_engine_t;
+
+typedef enum {
+	CAMEL_IMAP4_LEVEL_UNKNOWN,
+	CAMEL_IMAP4_LEVEL_IMAP4,
+	CAMEL_IMAP4_LEVEL_IMAP4REV1
+} camel_imap4_level_t;
+
+enum {
+	CAMEL_IMAP4_CAPABILITY_IMAP4            = (1 << 0),
+	CAMEL_IMAP4_CAPABILITY_IMAP4REV1        = (1 << 1),
+	CAMEL_IMAP4_CAPABILITY_STATUS           = (1 << 2),
+	CAMEL_IMAP4_CAPABILITY_NAMESPACE        = (1 << 3),
+	CAMEL_IMAP4_CAPABILITY_UIDPLUS          = (1 << 4),
+	CAMEL_IMAP4_CAPABILITY_LITERALPLUS      = (1 << 5),
+	CAMEL_IMAP4_CAPABILITY_LOGINDISABLED    = (1 << 6),
+	CAMEL_IMAP4_CAPABILITY_STARTTLS         = (1 << 7),
+	CAMEL_IMAP4_CAPABILITY_IDLE             = (1 << 8),
+	CAMEL_IMAP4_CAPABILITY_QUOTA            = (1 << 9),
+	CAMEL_IMAP4_CAPABILITY_ACL              = (1 << 10),
+	CAMEL_IMAP4_CAPABILITY_MULTIAPPEND      = (1 << 11),
+	CAMEL_IMAP4_CAPABILITY_UNSELECT         = (1 << 12),
+	
+	CAMEL_IMAP4_CAPABILITY_XGWEXTENSIONS    = (1 << 16),
+	CAMEL_IMAP4_CAPABILITY_XGWMOVE          = (1 << 17),
+	
+	CAMEL_IMAP4_CAPABILITY_useful_lsub      = (1 << 30),
+	CAMEL_IMAP4_CAPABILITY_utf8_search      = (1 << 31),
+};
+
+typedef enum {
+	CAMEL_IMAP4_RESP_CODE_ALERT,
+	CAMEL_IMAP4_RESP_CODE_BADCHARSET,
+	CAMEL_IMAP4_RESP_CODE_CAPABILITY,
+	CAMEL_IMAP4_RESP_CODE_PARSE,
+	CAMEL_IMAP4_RESP_CODE_PERM_FLAGS,
+	CAMEL_IMAP4_RESP_CODE_READONLY,
+	CAMEL_IMAP4_RESP_CODE_READWRITE,
+	CAMEL_IMAP4_RESP_CODE_TRYCREATE,
+	CAMEL_IMAP4_RESP_CODE_UIDNEXT,
+	CAMEL_IMAP4_RESP_CODE_UIDVALIDITY,
+	CAMEL_IMAP4_RESP_CODE_UNSEEN,
+	CAMEL_IMAP4_RESP_CODE_NEWNAME,
+	CAMEL_IMAP4_RESP_CODE_APPENDUID,
+	CAMEL_IMAP4_RESP_CODE_COPYUID,
+	CAMEL_IMAP4_RESP_CODE_UNKNOWN,
+} camel_imap4_resp_code_t;
+
+typedef struct _CamelIMAP4RespCode {
+	camel_imap4_resp_code_t code;
+	union {
+		guint32 flags;
+		char *parse;
+		guint32 uidnext;
+		guint32 uidvalidity;
+		guint32 unseen;
+		char *newname[2];
+		struct {
+			guint32 uidvalidity;
+			guint32 uid;
+		} appenduid;
+		struct {
+			guint32 uidvalidity;
+			char *srcset;
+			char *destset;
+		} copyuid;
+	} v;
+} CamelIMAP4RespCode;
+
+enum {
+	CAMEL_IMAP4_UNTAGGED_ERROR = -1,
+	CAMEL_IMAP4_UNTAGGED_OK,
+	CAMEL_IMAP4_UNTAGGED_NO,
+	CAMEL_IMAP4_UNTAGGED_BAD,
+	CAMEL_IMAP4_UNTAGGED_PREAUTH,
+	CAMEL_IMAP4_UNTAGGED_HANDLED,
+};
+
+typedef struct _CamelIMAP4Namespace {
+	struct _CamelIMAP4Namespace *next;
+	char *path;
+	char sep;
+} CamelIMAP4Namespace;
+
+typedef struct _CamelIMAP4NamespaceList {
+	CamelIMAP4Namespace *personal;
+	CamelIMAP4Namespace *other;
+	CamelIMAP4Namespace *shared;
+} CamelIMAP4NamespaceList;
+
+enum {
+	CAMEL_IMAP4_ENGINE_MAXLEN_LINE,
+	CAMEL_IMAP4_ENGINE_MAXLEN_TOKEN
+};
+
+typedef gboolean (* CamelIMAP4ReconnectFunc) (CamelIMAP4Engine *engine, CamelException *ex);
+
+struct _CamelIMAP4Engine {
+	CamelObject parent_object;
+	
+	CamelIMAP4ReconnectFunc reconnect;
+	gboolean reconnecting;
+	
+	CamelSession *session;
+	CamelService *service;
+	CamelURL *url;
+	
+	camel_imap4_engine_t state;
+	camel_imap4_level_t level;
+	guint32 capa;
+	
+	guint32 maxlen:31;
+	guint32 maxlentype:1;
+	
+	CamelIMAP4NamespaceList namespaces;
+	GHashTable *authtypes;                    /* supported authtypes */
+	
+	struct _CamelIMAP4Stream *istream;
+	CamelStream *ostream;
+	
+	unsigned char tagprefix;             /* 'A'..'Z' */
+	unsigned int tag;                    /* next command tag */
+	int nextid;
+	
+	struct _CamelIMAP4Folder *folder;    /* currently selected folder */
+	
+	EDList queue;                          /* queue of waiting commands */
+	struct _CamelIMAP4Command *current;
+};
+
+struct _CamelIMAP4EngineClass {
+	CamelObjectClass parent_class;
+	
+	unsigned char tagprefix;
+};
+
+
+CamelType camel_imap4_engine_get_type (void);
+
+CamelIMAP4Engine *camel_imap4_engine_new (CamelService *service, CamelIMAP4ReconnectFunc reconnect);
+
+/* returns 0 on success or -1 on error */
+int camel_imap4_engine_take_stream (CamelIMAP4Engine *engine, CamelStream *stream, CamelException *ex);
+
+int camel_imap4_engine_capability (CamelIMAP4Engine *engine, CamelException *ex);
+int camel_imap4_engine_namespace (CamelIMAP4Engine *engine, CamelException *ex);
+
+int camel_imap4_engine_select_folder (CamelIMAP4Engine *engine, CamelFolder *folder, CamelException *ex);
+
+struct _CamelIMAP4Command *camel_imap4_engine_queue (CamelIMAP4Engine *engine, CamelFolder *folder,
+						     const char *format, ...);
+struct _CamelIMAP4Command *camel_imap4_engine_prequeue (CamelIMAP4Engine *engine, CamelFolder *folder,
+							const char *format, ...);
+
+void camel_imap4_engine_dequeue (CamelIMAP4Engine *engine, struct _CamelIMAP4Command *ic);
+
+int camel_imap4_engine_iterate (CamelIMAP4Engine *engine);
+
+
+/* untagged response utility functions */
+int camel_imap4_engine_handle_untagged_1 (CamelIMAP4Engine *engine, struct _camel_imap4_token_t *token, CamelException *ex);
+void camel_imap4_engine_handle_untagged (CamelIMAP4Engine *engine, CamelException *ex);
+
+/* stream wrapper utility functions */
+int camel_imap4_engine_next_token (CamelIMAP4Engine *engine, struct _camel_imap4_token_t *token, CamelException *ex);
+int camel_imap4_engine_line (CamelIMAP4Engine *engine, unsigned char **line, size_t *len, CamelException *ex);
+int camel_imap4_engine_literal (CamelIMAP4Engine *engine, unsigned char **literal, size_t *len, CamelException *ex);
+int camel_imap4_engine_nstring (CamelIMAP4Engine *engine, unsigned char **nstring, CamelException *ex);
+int camel_imap4_engine_eat_line (CamelIMAP4Engine *engine, CamelException *ex);
+
+
+/* response code stuff */
+int camel_imap4_engine_parse_resp_code (CamelIMAP4Engine *engine, CamelException *ex);
+void camel_imap4_resp_code_free (CamelIMAP4RespCode *rcode);
+
+G_END_DECLS
+
+#endif /* __CAMEL_IMAP4_ENGINE_H__ */

Added: trunk/imap4/camel-imap4-folder.c
==============================================================================
--- (empty file)
+++ trunk/imap4/camel-imap4-folder.c	Sat May 10 17:53:05 2008
@@ -0,0 +1,1252 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*  Camel
+ *  Copyright (C) 1999-2007 Novell, Inc. (www.novell.com)
+ *
+ *  Authors: Jeffrey Stedfast <fejj novell com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU Lesser General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  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 Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <time.h>
+
+#include <glib.h>
+#include <glib/gi18n-lib.h>
+
+#include <libedataserver/e-data-server-util.h>
+
+#include <camel/camel-private.h>
+#include <camel/camel-file-utils.h>
+#include <camel/camel-stream-filter.h>
+#include <camel/camel-mime-filter-crlf.h>
+#include <camel/camel-mime-message.h>
+#include <camel/camel-stream-mem.h>
+#include <camel/camel-utf8.h>
+
+#include "camel-imap4-utils.h"
+#include "camel-imap4-store.h"
+#include "camel-imap4-engine.h"
+#include "camel-imap4-folder.h"
+#include "camel-imap4-journal.h"
+#include "camel-imap4-stream.h"
+#include "camel-imap4-command.h"
+#include "camel-imap4-summary.h"
+#include "camel-imap4-search.h"
+
+#define d(x) x
+
+static void camel_imap4_folder_class_init (CamelIMAP4FolderClass *klass);
+static void camel_imap4_folder_init (CamelIMAP4Folder *folder, CamelIMAP4FolderClass *klass);
+static void camel_imap4_folder_finalize (CamelObject *object);
+
+static int imap4_getv (CamelObject *object, CamelException *ex, CamelArgGetV *args);
+static int imap4_setv (CamelObject *object, CamelException *ex, CamelArgV *args);
+
+static void imap4_sync (CamelFolder *folder, gboolean expunge, CamelException *ex);
+static void imap4_refresh_info (CamelFolder *folder, CamelException *ex);
+static void imap4_expunge (CamelFolder *folder, CamelException *ex);
+static CamelMimeMessage *imap4_get_message (CamelFolder *folder, const char *uid, CamelException *ex);
+static void imap4_append_message (CamelFolder *folder, CamelMimeMessage *message,
+				  const CamelMessageInfo *info, char **appended_uid, CamelException *ex);
+static void imap4_transfer_messages_to (CamelFolder *src, GPtrArray *uids, CamelFolder *dest,
+					GPtrArray **transferred_uids, gboolean move, CamelException *ex);
+static GPtrArray *imap4_search_by_expression (CamelFolder *folder, const char *expr, CamelException *ex);
+static GPtrArray *imap4_search_by_uids (CamelFolder *folder, const char *expr, GPtrArray *uids, CamelException *ex);
+static void imap4_search_free (CamelFolder *folder, GPtrArray *uids);
+
+
+static CamelOfflineFolderClass *parent_class = NULL;
+
+
+static GSList *imap4_folder_props = NULL;
+
+static CamelProperty imap4_prop_list[] = {
+	{ CAMEL_IMAP4_FOLDER_ENABLE_MLIST, "mlist_info", N_("Enable Mailing-List detection required for some filter and vFolder rules") },
+};
+
+
+CamelType
+camel_imap4_folder_get_type (void)
+{
+	static CamelType type = 0;
+	
+	if (!type) {
+		type = camel_type_register (camel_offline_folder_get_type (),
+					    "CamelIMAP4Folder",
+					    sizeof (CamelIMAP4Folder),
+					    sizeof (CamelIMAP4FolderClass),
+					    (CamelObjectClassInitFunc) camel_imap4_folder_class_init,
+					    NULL,
+					    (CamelObjectInitFunc) camel_imap4_folder_init,
+					    (CamelObjectFinalizeFunc) camel_imap4_folder_finalize);
+	}
+	
+	return type;
+}
+
+static void
+camel_imap4_folder_class_init (CamelIMAP4FolderClass *klass)
+{
+	CamelFolderClass *folder_class = (CamelFolderClass *) klass;
+	CamelObjectClass *object_class = (CamelObjectClass *) klass;
+	int i;
+	
+	parent_class = (CamelOfflineFolderClass *) camel_type_get_global_classfuncs (CAMEL_OFFLINE_FOLDER_TYPE);
+	
+	if (imap4_folder_props == NULL) {
+		for (i = 0; i < G_N_ELEMENTS (imap4_prop_list); i++) {
+			imap4_prop_list[i].description = _(imap4_prop_list[i].description);
+			imap4_folder_props = g_slist_prepend (imap4_folder_props, &imap4_prop_list[i]);
+		}
+	}
+	
+	object_class->getv = imap4_getv;
+	object_class->setv = imap4_setv;
+	
+	folder_class->sync = imap4_sync;
+	folder_class->refresh_info = imap4_refresh_info;
+	folder_class->expunge = imap4_expunge;
+	folder_class->get_message = imap4_get_message;
+	folder_class->append_message = imap4_append_message;
+	folder_class->transfer_messages_to = imap4_transfer_messages_to;
+	folder_class->search_by_expression = imap4_search_by_expression;
+	folder_class->search_by_uids = imap4_search_by_uids;
+	folder_class->search_free = imap4_search_free;
+}
+
+static void
+camel_imap4_folder_init (CamelIMAP4Folder *folder, CamelIMAP4FolderClass *klass)
+{
+	((CamelFolder *) folder)->folder_flags |= CAMEL_FOLDER_HAS_SUMMARY_CAPABILITY | CAMEL_FOLDER_HAS_SEARCH_CAPABILITY;
+	
+	folder->utf7_name = NULL;
+	folder->cachedir = NULL;
+	folder->journal = NULL;
+	folder->search = NULL;
+}
+
+static void
+camel_imap4_folder_finalize (CamelObject *object)
+{
+	CamelIMAP4Folder *folder = (CamelIMAP4Folder *) object;
+	
+	camel_object_unref (folder->search);
+	
+	if (folder->cache)
+		camel_object_unref (folder->cache);
+	
+	if (folder->journal) {
+		camel_offline_journal_write (folder->journal, NULL);
+		camel_object_unref (folder->journal);
+	}
+	
+	g_free (folder->utf7_name);
+	g_free (folder->cachedir);
+}
+
+static int
+imap4_getv (CamelObject *object, CamelException *ex, CamelArgGetV *args)
+{
+	CamelArgGetV props;
+	int i, count = 0;
+	guint32 tag;
+	
+	for (i = 0; i < args->argc; i++) {
+		CamelArgGet *arg = &args->argv[i];
+		
+		tag = arg->tag;
+		
+		switch (tag & CAMEL_ARG_TAG) {
+		case CAMEL_OBJECT_ARG_PERSISTENT_PROPERTIES:
+		case CAMEL_FOLDER_ARG_PROPERTIES:
+			props.argc = 1;
+			props.argv[0] = *arg;
+			((CamelObjectClass *) parent_class)->getv (object, ex, &props);
+			*arg->ca_ptr = g_slist_concat (*arg->ca_ptr, g_slist_copy (imap4_folder_props));
+			break;
+		case CAMEL_IMAP4_FOLDER_ARG_ENABLE_MLIST:
+			*arg->ca_int = ((CamelIMAP4Folder *) object)->enable_mlist;
+			break;
+		default:
+			count++;
+			continue;
+		}
+		
+		arg->tag = (tag & CAMEL_ARG_TYPE) | CAMEL_ARG_IGNORE;
+	}
+	
+	if (count)
+		return ((CamelObjectClass *) parent_class)->getv (object, ex, args);
+	
+	return 0;
+}
+
+static int
+imap4_setv (CamelObject *object, CamelException *ex, CamelArgV *args)
+{
+	CamelIMAP4Folder *folder = (CamelIMAP4Folder *) object;
+	gboolean save = FALSE;
+	guint32 tag;
+	int i;
+	
+	for (i = 0; i < args->argc; i++) {
+		CamelArg *arg = &args->argv[i];
+		
+		tag = arg->tag;
+		
+		switch (tag & CAMEL_ARG_TAG) {
+		case CAMEL_IMAP4_FOLDER_ARG_ENABLE_MLIST:
+			if (folder->enable_mlist != arg->ca_int) {
+				folder->enable_mlist = arg->ca_int;
+				save = TRUE;
+			}
+			break;
+		default:
+			continue;
+		}
+		
+		arg->tag = (tag & CAMEL_ARG_TYPE) | CAMEL_ARG_IGNORE;
+	}
+	
+	if (save)
+		camel_object_state_write (object);
+	
+	return ((CamelObjectClass *) parent_class)->setv (object, ex, args);
+}
+
+
+static char *
+imap4_get_summary_filename (const char *path)
+{
+	/* /path/to/imap/summary */
+	return g_build_filename (path, "summary", NULL);
+}
+
+static char *
+imap4_get_journal_filename (const char *path)
+{
+	/* /path/to/imap/journal */
+	return g_build_filename (path, "journal", NULL);
+}
+
+static char *
+imap4_build_filename (const char *toplevel_dir, const char *full_name)
+{
+	const char *inptr = full_name;
+	int subdirs = 0;
+	char *path, *p;
+	
+	if (*full_name == '\0')
+		return g_strdup (toplevel_dir);
+	
+	while (*inptr != '\0') {
+		if (*inptr == '/')
+			subdirs++;
+		inptr++;
+	}
+	
+	path = g_malloc (strlen (toplevel_dir) + (inptr - full_name) + (12 * subdirs) + 2);
+	p = g_stpcpy (path, toplevel_dir);
+	
+	if (p[-1] != '/')
+		*p++ = '/';
+	
+	inptr = full_name;
+	while (*inptr != '\0') {
+		while (*inptr != '/' && *inptr != '\0')
+			*p++ = *inptr++;
+		
+		if (*inptr == '/') {
+			p = g_stpcpy (p, "/subfolders/");
+			inptr++;
+			
+			/* strip extranaeous '/'s */
+			while (*inptr == '/')
+				inptr++;
+		}
+	}
+	
+	*p = '\0';
+	
+	return path;
+}
+
+static char *
+imap4_store_build_filename (void *store, const char *full_name)
+{
+	CamelIMAP4Store *imap4_store = (CamelIMAP4Store *) store;
+	char *toplevel_dir;
+	char *path;
+	
+	toplevel_dir = g_strdup_printf ("%s/folders", imap4_store->storage_path);
+	path = imap4_build_filename (toplevel_dir, full_name);
+	g_free (toplevel_dir);
+	
+	return path;
+}
+
+
+CamelFolder *
+camel_imap4_folder_new (CamelStore *store, const char *full_name, CamelException *ex)
+{
+	CamelIMAP4Folder *imap4_folder;
+	char *utf7_name, *name, *p;
+	CamelFolder *folder;
+	char *path;
+	char sep;
+	
+	if (!(p = strrchr (full_name, '/')))
+		p = (char *) full_name;
+	else
+		p++;
+	
+	name = g_alloca (strlen (p) + 1);
+	strcpy (name, p);
+	
+	utf7_name = g_alloca (strlen (full_name) + 1);
+	strcpy (utf7_name, full_name);
+	
+	sep = camel_imap4_get_path_delim (((CamelIMAP4Store *) store)->summary, full_name);
+	if (sep != '/') {
+		p = utf7_name;
+		while (*p != '\0') {
+			if (*p == '/')
+				*p = sep;
+			p++;
+		}
+	}
+	
+	utf7_name = camel_utf8_utf7 (utf7_name);
+	
+	folder = (CamelFolder *) (imap4_folder = (CamelIMAP4Folder *) camel_object_new (CAMEL_TYPE_IMAP4_FOLDER));
+	camel_folder_construct (folder, store, full_name, name);
+	imap4_folder->utf7_name = utf7_name;
+	
+	folder->summary = camel_imap4_summary_new (folder);
+	imap4_folder->cachedir = imap4_store_build_filename (store, folder->full_name);
+	g_mkdir_with_parents (imap4_folder->cachedir, 0777);
+	
+	imap4_folder->cache = camel_data_cache_new (imap4_folder->cachedir, 0, NULL);
+	
+	path = imap4_get_summary_filename (imap4_folder->cachedir);
+	camel_folder_summary_set_filename (folder->summary, path);
+	g_free (path);
+	
+	path = imap4_get_journal_filename (imap4_folder->cachedir);
+	imap4_folder->journal = camel_imap4_journal_new (imap4_folder, path);
+	g_free (path);
+	
+	path = g_build_filename (imap4_folder->cachedir, "cmeta", NULL);
+	camel_object_set (folder, NULL, CAMEL_OBJECT_STATE_FILE, path, NULL);
+	g_free (path);
+	
+	if (camel_object_state_read (folder) == -1) {
+		/* set our defaults */
+		imap4_folder->enable_mlist = TRUE;
+	}
+	
+	if (!g_ascii_strcasecmp (full_name, "INBOX")) {
+		if (camel_url_get_param (((CamelService *) store)->url, "filter"))
+			folder->folder_flags |= CAMEL_FOLDER_FILTER_RECENT;
+		if (camel_url_get_param (((CamelService *) store)->url, "filter_junk"))
+			folder->folder_flags |= CAMEL_FOLDER_FILTER_JUNK;
+	} else if (!camel_url_get_param (((CamelService *) store)->url, "filter_junk_inbox")) {
+		if (camel_url_get_param (((CamelService *) store)->url, "filter_junk"))
+			folder->folder_flags |= CAMEL_FOLDER_FILTER_JUNK;
+	}
+	
+	imap4_folder->search = camel_imap4_search_new (((CamelIMAP4Store *) store)->engine, imap4_folder->cachedir);
+	
+	if (((CamelOfflineStore *) store)->state == CAMEL_OFFLINE_STORE_NETWORK_AVAIL) {
+		/* we don't care if the summary loading fails here */
+		camel_folder_summary_load (folder->summary);
+		
+		if (camel_imap4_engine_select_folder (((CamelIMAP4Store *) store)->engine, folder, ex) == -1) {
+			camel_object_unref (folder);
+			folder = NULL;
+		}
+		
+		if (folder && camel_imap4_summary_flush_updates (folder->summary, ex) == -1) {
+			camel_object_unref (folder);
+			folder = NULL;
+		}
+	} else {
+		/* we *do* care if summary loading fails here though */
+		if (camel_folder_summary_load (folder->summary) == -1) {
+			camel_exception_setv (ex, CAMEL_EXCEPTION_FOLDER_INVALID_PATH,
+					      _("Cannot access folder `%s': %s"),
+					      full_name, g_strerror (ENOENT));
+			
+			camel_object_unref (folder);
+			folder = NULL;
+		}
+	}
+	
+	return folder;
+}
+
+const char *
+camel_imap4_folder_utf7_name (CamelIMAP4Folder *folder)
+{
+	return folder->utf7_name;
+}
+
+
+static struct {
+	const char *name;
+	guint32 flag;
+} imap4_flags[] = {
+	{ "\\Answered", CAMEL_MESSAGE_ANSWERED  },
+	{ "\\Deleted",  CAMEL_MESSAGE_DELETED   },
+	{ "\\Draft",    CAMEL_MESSAGE_DRAFT     },
+	{ "\\Flagged",  CAMEL_MESSAGE_FLAGGED   },
+	/*{ "$Forwarded",  CAMEL_MESSAGE_FORWARDED },*/
+	{ "\\Seen",     CAMEL_MESSAGE_SEEN      },
+};
+
+
+static int
+imap4_sync_flag (CamelFolder *folder, GPtrArray *infos, char onoff, const char *flag, CamelException *ex)
+{
+	CamelIMAP4Engine *engine = ((CamelIMAP4Store *) folder->parent_store)->engine;
+	CamelIMAP4Command *ic;
+	int i, id, retval = 0;
+	char *set = NULL;
+	
+	for (i = 0; i < infos->len; ) {
+		i += camel_imap4_get_uid_set (engine, folder->summary, infos, i, 30 + strlen (flag), &set);
+		
+		ic = camel_imap4_engine_queue (engine, folder, "UID STORE %s %cFLAGS.SILENT (%s)\r\n", set, onoff, flag);
+		while ((id = camel_imap4_engine_iterate (engine)) < ic->id && id != -1)
+			;
+		
+		g_free (set);
+		
+		if (id == -1 || ic->status != CAMEL_IMAP4_COMMAND_COMPLETE) {
+			camel_exception_xfer (ex, &ic->ex);
+			camel_imap4_command_unref (ic);
+			
+			return -1;
+		}
+		
+		switch (ic->result) {
+		case CAMEL_IMAP4_RESULT_NO:
+			/* FIXME: would be good to save the NO reason into the err message */
+			camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+					      _("Cannot sync flags to folder `%s': Unknown"),
+					      folder->full_name);
+			retval = -1;
+			break;
+		case CAMEL_IMAP4_RESULT_BAD:
+			camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+					      _("Cannot sync flags to folder `%s': Bad command"),
+					      folder->full_name);
+			retval = -1;
+			break;
+		}
+		
+		camel_imap4_command_unref (ic);
+		
+		if (retval == -1)
+			return -1;
+	}
+	
+	return 0;
+}
+
+static int
+imap4_sync_changes (CamelFolder *folder, GPtrArray *sync, CamelException *ex)
+{
+	CamelIMAP4MessageInfo *iinfo;
+	GPtrArray *on_set, *off_set;
+	CamelMessageInfo *info;
+	flags_diff_t diff;
+	int retval = 0;
+	int i, j;
+	
+	on_set = g_ptr_array_new ();
+	off_set = g_ptr_array_new ();
+	
+	/* construct commands to sync system and user flags */
+	for (i = 0; i < G_N_ELEMENTS (imap4_flags); i++) {
+		if (!(imap4_flags[i].flag & folder->permanent_flags))
+			continue;
+		
+		for (j = 0; j < sync->len; j++) {
+			iinfo = (CamelIMAP4MessageInfo *) (info = sync->pdata[j]);
+			camel_imap4_flags_diff (&diff, iinfo->server_flags, iinfo->info.flags);
+			if (diff.changed & imap4_flags[i].flag) {
+				if (diff.bits & imap4_flags[i].flag) {
+					g_ptr_array_add (on_set, info);
+				} else {
+					g_ptr_array_add (off_set, info);
+				}
+			}
+		}
+		
+		if (on_set->len > 0) {
+			if ((retval = imap4_sync_flag (folder, on_set, '+', imap4_flags[i].name, ex)) == -1)
+				break;
+			
+			g_ptr_array_set_size (on_set, 0);
+		}
+		
+		if (off_set->len > 0) {
+			if ((retval = imap4_sync_flag (folder, off_set, '-', imap4_flags[i].name, ex)) == -1)
+				break;
+			
+			g_ptr_array_set_size (off_set, 0);
+		}
+	}
+	
+	g_ptr_array_free (on_set, TRUE);
+	g_ptr_array_free (off_set, TRUE);
+	
+	if (retval == -1)
+		return-1;
+	
+	for (i = 0; i < sync->len; i++) {
+		iinfo = (CamelIMAP4MessageInfo *) (info = sync->pdata[i]);
+		iinfo->info.flags &= ~CAMEL_MESSAGE_FOLDER_FLAGGED;
+		iinfo->server_flags = iinfo->info.flags & folder->permanent_flags;
+	}
+	
+	return 0;
+}
+
+static void
+imap4_sync (CamelFolder *folder, gboolean expunge, CamelException *ex)
+{
+	CamelIMAP4Engine *engine = ((CamelIMAP4Store *) folder->parent_store)->engine;
+	CamelOfflineStore *offline = (CamelOfflineStore *) folder->parent_store;
+	CamelIMAP4MessageInfo *iinfo;
+	CamelMessageInfo *info;
+	CamelIMAP4Command *ic;
+	flags_diff_t diff;
+	GPtrArray *sync;
+	int id, max, i;
+	int retval;
+	
+	if (offline->state == CAMEL_OFFLINE_STORE_NETWORK_UNAVAIL)
+		return;
+	
+	CAMEL_SERVICE_REC_LOCK (folder->parent_store, connect_lock);
+	
+	/* gather a list of changes to sync to the server */
+	if (folder->permanent_flags) {
+		sync = g_ptr_array_new ();
+		max = camel_folder_summary_count (folder->summary);
+		for (i = 0; i < max; i++) {
+			iinfo = (CamelIMAP4MessageInfo *) (info = camel_folder_summary_index (folder->summary, i));
+			if (iinfo->info.flags & CAMEL_MESSAGE_FOLDER_FLAGGED) {
+				camel_imap4_flags_diff (&diff, iinfo->server_flags, iinfo->info.flags);
+				diff.changed &= folder->permanent_flags;
+				
+				/* weed out flag changes that we can't sync to the server */
+				if (!diff.changed)
+					camel_message_info_free(info);
+				else
+					g_ptr_array_add (sync, info);
+			} else {
+				camel_message_info_free(info);
+			}
+		}
+		
+		if (sync->len > 0) {
+			retval = imap4_sync_changes (folder, sync, ex);
+			
+			for (i = 0; i < sync->len; i++)
+				camel_message_info_free(sync->pdata[i]);
+			
+			g_ptr_array_free (sync, TRUE);
+			
+			if (retval == -1)
+				goto done;
+		} else {
+			g_ptr_array_free (sync, TRUE);
+		}
+	}
+	
+	if (expunge && !((CamelIMAP4Folder *) folder)->read_only) {
+		ic = camel_imap4_engine_queue (engine, folder, "EXPUNGE\r\n");
+		while ((id = camel_imap4_engine_iterate (engine)) < ic->id && id != -1)
+			;
+		
+		switch (ic->result) {
+		case CAMEL_IMAP4_RESULT_OK:
+			camel_imap4_summary_flush_updates (folder->summary, ex);
+			break;
+		case CAMEL_IMAP4_RESULT_NO:
+			/* FIXME: would be good to save the NO reason into the err message */
+			camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+					      _("Cannot expunge folder `%s': Unknown"),
+					      folder->full_name);
+			break;
+		case CAMEL_IMAP4_RESULT_BAD:
+			camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+					      _("Cannot expunge folder `%s': Bad command"),
+					      folder->full_name);
+			break;
+		}
+		
+		camel_imap4_command_unref (ic);
+	} else {
+		camel_imap4_summary_flush_updates (folder->summary, ex);
+	}
+	
+	camel_folder_summary_save (folder->summary);
+	
+ done:
+	
+	CAMEL_SERVICE_REC_UNLOCK (folder->parent_store, connect_lock);
+}
+
+static void
+imap4_expunge (CamelFolder *folder, CamelException *ex)
+{
+	imap4_sync (folder, TRUE, ex);
+}
+
+static void
+imap4_refresh_info (CamelFolder *folder, CamelException *ex)
+{
+	CamelIMAP4Engine *engine = ((CamelIMAP4Store *) folder->parent_store)->engine;
+	CamelOfflineStore *offline = (CamelOfflineStore *) folder->parent_store;
+	CamelFolder *selected = (CamelFolder *) engine->folder;
+	CamelIMAP4Command *ic;
+	int id;
+	
+	if (offline->state == CAMEL_OFFLINE_STORE_NETWORK_UNAVAIL)
+		return;
+	
+	CAMEL_SERVICE_REC_LOCK (folder->parent_store, connect_lock);
+	
+	if (folder != selected) {
+		if (camel_imap4_engine_select_folder (engine, folder, ex) == -1)
+			goto done;
+		
+		((CamelIMAP4Summary *) folder->summary)->update_flags = TRUE;
+	} else {
+		ic = camel_imap4_engine_queue (engine, NULL, "NOOP\r\n");
+		while ((id = camel_imap4_engine_iterate (engine)) < ic->id && id != -1)
+			;
+		
+		if (id == -1 || ic->status != CAMEL_IMAP4_COMMAND_COMPLETE)
+			camel_exception_xfer (ex, &ic->ex);
+		
+		camel_imap4_command_unref (ic);
+		
+		if (camel_exception_is_set (ex))
+			goto done;
+	}
+	
+	camel_imap4_summary_flush_updates (folder->summary, ex);
+	
+ done:
+	
+	CAMEL_SERVICE_REC_UNLOCK (folder->parent_store, connect_lock);
+}
+
+static int
+untagged_fetch (CamelIMAP4Engine *engine, CamelIMAP4Command *ic, guint32 index, camel_imap4_token_t *token, CamelException *ex)
+{
+	CamelFolderSummary *summary = ((CamelFolder *) engine->folder)->summary;
+	CamelStream *fstream, *stream = ic->user_data;
+	CamelFolderChangeInfo *changes;
+	CamelIMAP4MessageInfo *iinfo;
+	CamelMessageInfo *info;
+	CamelMimeFilter *crlf;
+	guint32 flags;
+	
+	if (camel_imap4_engine_next_token (engine, token, ex) == -1)
+		return -1;
+	
+	/* parse the FETCH response list */
+	if (token->token != '(') {
+		camel_imap4_utils_set_unexpected_token_error (ex, engine, token);
+		return -1;
+	}
+	
+	do {
+		if (camel_imap4_engine_next_token (engine, token, ex) == -1)
+			goto exception;
+
+		if (token->token == ')' || token->token == '\n')
+			break;
+		
+		if (token->token != CAMEL_IMAP4_TOKEN_ATOM)
+			goto unexpected;
+		
+		if (!strcmp (token->v.atom, "BODY[")) {
+			if (camel_imap4_engine_next_token (engine, token, ex) == -1)
+				goto exception;
+			
+			if (token->token != ']')
+				goto unexpected;
+			
+			if (camel_imap4_engine_next_token (engine, token, ex) == -1)
+				goto exception;
+			
+			if (token->token != CAMEL_IMAP4_TOKEN_LITERAL)
+				goto unexpected;
+			
+			fstream = (CamelStream *) camel_stream_filter_new_with_stream (stream);
+			crlf = camel_mime_filter_crlf_new (CAMEL_MIME_FILTER_CRLF_DECODE, CAMEL_MIME_FILTER_CRLF_MODE_CRLF_ONLY);
+			camel_stream_filter_add ((CamelStreamFilter *) fstream, crlf);
+			camel_object_unref (crlf);
+			
+			camel_stream_write_to_stream ((CamelStream *) engine->istream, fstream);
+			camel_stream_flush (fstream);
+			camel_object_unref (fstream);
+		} else if (!strcmp (token->v.atom, "UID")) {
+			if (camel_imap4_engine_next_token (engine, token, ex) == -1)
+				goto exception;
+			
+			if (token->token != CAMEL_IMAP4_TOKEN_NUMBER || token->v.number == 0)
+				goto unexpected;
+		} else if (!strcmp (token->v.atom, "FLAGS")) {
+			/* even though we didn't request this bit of information, it might be
+			 * given to us if another client recently changed the flags... */
+			if (camel_imap4_parse_flags_list (engine, &flags, ex) == -1)
+				goto exception;
+			
+			if ((info = camel_folder_summary_index (summary, index - 1))) {
+				iinfo = (CamelIMAP4MessageInfo *) info;
+				iinfo->info.flags = camel_imap4_merge_flags (iinfo->server_flags, iinfo->info.flags, flags);
+				iinfo->server_flags = flags;
+				
+				changes = camel_folder_change_info_new ();
+				camel_folder_change_info_change_uid (changes, camel_message_info_uid (info));
+				camel_object_trigger_event (engine->folder, "folder_changed", changes);
+				camel_folder_change_info_free (changes);
+				
+				camel_message_info_free(info);
+			}
+		} else {
+			/* wtf? */
+			d(fprintf (stderr, "huh? %s?...\n", token->v.atom));
+		}
+	} while (1);
+	
+	if (token->token != ')') {
+		d(fprintf (stderr, "expected ')' to close untagged FETCH response\n"));
+		goto unexpected;
+	}
+	
+	return 0;
+	
+ unexpected:
+	
+	camel_imap4_utils_set_unexpected_token_error (ex, engine, token);
+	
+ exception:
+	
+	return -1;
+}
+
+static CamelMimeMessage *
+imap4_get_message (CamelFolder *folder, const char *uid, CamelException *ex)
+{
+	CamelIMAP4Engine *engine = ((CamelIMAP4Store *) folder->parent_store)->engine;
+	CamelOfflineStore *offline = (CamelOfflineStore *) folder->parent_store;
+	CamelIMAP4Folder *imap4_folder = (CamelIMAP4Folder *) folder;
+	CamelMimeMessage *message = NULL;
+	CamelStream *stream, *cache;
+	CamelIMAP4Command *ic;
+	int id;
+	
+	CAMEL_SERVICE_REC_LOCK (folder->parent_store, connect_lock);
+	
+	if (imap4_folder->cache && (stream = camel_data_cache_get (imap4_folder->cache, "cache", uid, ex))) {
+		message = camel_mime_message_new ();
+		
+		if (camel_data_wrapper_construct_from_stream ((CamelDataWrapper *) message, stream) == -1) {
+			if (errno == EINTR) {
+				CAMEL_SERVICE_REC_UNLOCK (folder->parent_store, connect_lock);
+				camel_exception_setv (ex, CAMEL_EXCEPTION_USER_CANCEL, _("User canceled"));
+				camel_object_unref (message);
+				camel_object_unref (stream);
+				return NULL;
+			} else {
+				camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM, _("Cannot get message %s: %s"),
+						      uid, g_strerror (errno));
+				camel_object_unref (message);
+				message = NULL;
+			}
+		}
+		
+		camel_object_unref (stream);
+	}
+	
+	if (message != NULL) {
+		CAMEL_SERVICE_REC_UNLOCK (folder->parent_store, connect_lock);
+		return message;
+	}
+	
+	if (offline->state == CAMEL_OFFLINE_STORE_NETWORK_UNAVAIL) {
+		CAMEL_SERVICE_REC_UNLOCK (folder->parent_store, connect_lock);
+		camel_exception_set (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
+				     _("This message is not available in offline mode."));
+		return NULL;
+	}
+	
+	/* Note: While some hard-core IMAP extremists are probably
+	 * going to flame me for fetching entire messages here, it's
+	 * the *only* sure-fire way of working with all IMAP
+	 * servers. There are numerous problems with fetching
+	 * individual MIME parts from a good handful of IMAP servers
+	 * which makes this a pain to do the Right Way (tm). For
+	 * example: Courier-IMAP has "issues" parsing some multipart
+	 * messages apparently, because BODY responses are often
+	 * inaccurate. I'm also not very trusting of the free German
+	 * IMAP hosting either (such as mail.gmx.net and imap.web.de)
+	 * as they have proven themselves to be quite flakey wrt FETCH
+	 * requests (they seem to be written exclusively for
+	 * Outlook). Also, some IMAP servers such as GroupWise don't
+	 * store mail in MIME format and so must re-construct the
+	 * entire message in order to extract the requested part, so
+	 * it is *much* more efficient (generally) to just request the
+	 * entire message anyway. */
+	ic = camel_imap4_engine_queue (engine, folder, "UID FETCH %s BODY.PEEK[]\r\n", uid);
+	camel_imap4_command_register_untagged (ic, "FETCH", untagged_fetch);
+	ic->user_data = stream = camel_stream_mem_new ();
+	
+	while ((id = camel_imap4_engine_iterate (engine)) < ic->id && id != -1)
+		;
+	
+	if (id == -1 || ic->status != CAMEL_IMAP4_COMMAND_COMPLETE) {
+		camel_exception_xfer (ex, &ic->ex);
+		camel_imap4_command_unref (ic);
+		camel_object_unref (stream);
+		goto done;
+	}
+	
+	switch (ic->result) {
+	case CAMEL_IMAP4_RESULT_OK:
+		camel_stream_reset (stream);
+		message = camel_mime_message_new ();
+		camel_data_wrapper_construct_from_stream ((CamelDataWrapper *) message, stream);
+		camel_stream_reset (stream);
+		
+		/* cache the message locally */
+		if (imap4_folder->cache && (cache = camel_data_cache_add (imap4_folder->cache, "cache", uid, NULL))) {
+			if (camel_stream_write_to_stream (stream, cache) == -1
+			    || camel_stream_flush (cache) == -1)
+				camel_data_cache_remove (imap4_folder->cache, "cache", uid, NULL);
+			camel_object_unref (cache);
+		}
+		
+		break;
+	case CAMEL_IMAP4_RESULT_NO:
+		/* FIXME: would be good to save the NO reason into the err message */
+		camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+				      _("Cannot get message %s from folder `%s': No such message"),
+				      uid, folder->full_name);
+		break;
+	case CAMEL_IMAP4_RESULT_BAD:
+		camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+				      _("Cannot get message %s from folder `%s': Bad command"),
+				      uid, folder->full_name);
+		break;
+	}
+	
+	camel_imap4_command_unref (ic);
+	
+	camel_object_unref (stream);
+	
+ done:
+	
+	CAMEL_SERVICE_REC_UNLOCK (folder->parent_store, connect_lock);
+	
+	return message;
+}
+
+static char *tm_months[] = {
+	"Jan", "Feb", "Mar", "Apr", "May", "Jun",
+	"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
+};
+
+static void
+imap4_append_message (CamelFolder *folder, CamelMimeMessage *message,
+		      const CamelMessageInfo *info, char **appended_uid, CamelException *ex)
+{
+	CamelIMAP4Engine *engine = ((CamelIMAP4Store *) folder->parent_store)->engine;
+	CamelOfflineStore *offline = (CamelOfflineStore *) folder->parent_store;
+	CamelIMAP4Summary *summary = (CamelIMAP4Summary *) folder->summary;
+	const CamelIMAP4MessageInfo *iinfo = (const CamelIMAP4MessageInfo *) info;
+	CamelIMAP4Folder *imap4_folder = (CamelIMAP4Folder *) folder;
+	CamelIMAP4RespCode *resp;
+	CamelIMAP4Command *ic;
+	CamelFolderInfo *fi;
+	CamelException lex;
+	char flags[100], *p;
+	char date[50];
+	struct tm tm;
+	int id, i;
+	
+	if (appended_uid)
+		*appended_uid = NULL;
+	
+	if (((CamelIMAP4Folder *) folder)->read_only) {
+		camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+				      _("Cannot append message to folder `%s': Folder is read-only"),
+				      folder->full_name);
+		return;
+	}
+	
+	if (offline->state == CAMEL_OFFLINE_STORE_NETWORK_UNAVAIL) {
+		camel_imap4_journal_append ((CamelIMAP4Journal *) imap4_folder->journal, message, info, appended_uid, ex);
+		return;
+	}
+	
+	CAMEL_SERVICE_REC_LOCK (folder->parent_store, connect_lock);
+	
+	/* construct the option flags list */
+	if (iinfo->info.flags & folder->permanent_flags) {
+		p = g_stpcpy (flags, " (");
+		
+		for (i = 0; i < G_N_ELEMENTS (imap4_flags); i++) {
+			if ((iinfo->info.flags & imap4_flags[i].flag) & folder->permanent_flags) {
+				p = g_stpcpy (p, imap4_flags[i].name);
+				*p++ = ' ';
+			}
+		}
+		
+		p[-1] = ')';
+		*p = '\0';
+	} else {
+		flags[0] = '\0';
+	}
+	
+	/* construct the optional date_time string */
+	if (iinfo->info.date_received > (time_t) 0) {
+		int tzone;
+		
+#ifdef HAVE_LOCALTIME_R
+		localtime_r (&iinfo->info.date_received, &tm);
+#else
+		memcpy (&tm, localtime (&iinfo->info.date_received), sizeof (tm));
+#endif
+		
+#if defined (HAVE_TM_GMTOFF)
+		tzone = -tm.tm_gmtoff;
+#elif defined (HAVE_TIMEZONE)
+		if (tm.tm_isdst > 0) {
+#if defined (HAVE_ALTZONE)
+			tzone = altzone;
+#else /* !defined (HAVE_ALTZONE) */
+			tzone = (timezone - 3600);
+#endif
+		} else {
+			tzone = timezone;
+		}
+#else
+#error Neither HAVE_TIMEZONE nor HAVE_TM_GMTOFF defined. Rerun autoheader, autoconf, etc.
+#endif
+		
+		sprintf (date, " \"%02d-%s-%04d %02d:%02d:%02d %+05d\"",
+			 tm.tm_mday, tm_months[tm.tm_mon], tm.tm_year + 1900,
+			 tm.tm_hour, tm.tm_min, tm.tm_sec, tzone);
+	} else {
+		date[0] = '\0';
+	}
+	
+ retry:
+	
+	ic = camel_imap4_engine_queue (engine, NULL, "APPEND %F%s%s %L\r\n", folder, flags, date, message);
+	
+	while ((id = camel_imap4_engine_iterate (engine)) < ic->id && id != -1)
+		;
+	
+	if (id == -1 || ic->status != CAMEL_IMAP4_COMMAND_COMPLETE) {
+		camel_exception_xfer (ex, &ic->ex);
+		camel_imap4_command_unref (ic);
+		CAMEL_SERVICE_REC_UNLOCK (folder->parent_store, connect_lock);
+		return;
+	}
+	
+	switch (ic->result) {
+	case CAMEL_IMAP4_RESULT_OK:
+		if (!appended_uid || !(engine->capa & CAMEL_IMAP4_CAPABILITY_UIDPLUS))
+			break;
+		
+		for (i = 0; i < ic->resp_codes->len; i++) {
+			resp = ic->resp_codes->pdata[i];
+			if (resp->code == CAMEL_IMAP4_RESP_CODE_APPENDUID) {
+				if (resp->v.appenduid.uidvalidity == summary->uidvalidity)
+					*appended_uid = g_strdup_printf ("%u", resp->v.appenduid.uid);
+				break;
+			}
+		}
+		break;
+	case CAMEL_IMAP4_RESULT_NO:
+		/* FIXME: can we give the user any more information? */
+		camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+				      _("Cannot append message to folder `%s': Unknown error"),
+				      folder->full_name);
+		
+		for (i = 0; i < ic->resp_codes->len; i++) {
+			resp = ic->resp_codes->pdata[i];
+			if (resp->code == CAMEL_IMAP4_RESP_CODE_TRYCREATE) {
+				char *parent_name, *p;
+				
+				parent_name = g_alloca (strlen (folder->full_name) + 1);
+				strcpy (parent_name, folder->full_name);
+				if (!(p = strrchr (parent_name, '/')))
+					*parent_name = '\0';
+				else
+					*p = '\0';
+				
+				if (!(fi = camel_store_create_folder (folder->parent_store, parent_name, folder->name, &lex))) {
+					camel_exception_clear (&lex);
+					break;
+				}
+				
+				camel_store_free_folder_info (folder->parent_store, fi);
+				camel_imap4_command_unref (ic);
+				camel_exception_clear (ex);
+				goto retry;
+			}
+		}
+		
+		break;
+	case CAMEL_IMAP4_RESULT_BAD:
+		camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+				      _("Cannot append message to folder `%s': Bad command"),
+				      folder->full_name);
+		
+		break;
+	default:
+		g_assert_not_reached ();
+	}
+	
+	camel_imap4_command_unref (ic);
+	
+	CAMEL_SERVICE_REC_UNLOCK (folder->parent_store, connect_lock);
+}
+
+
+static int
+info_uid_sort (const CamelMessageInfo **info0, const CamelMessageInfo **info1)
+{
+	guint32 uid0, uid1;
+	
+	uid0 = strtoul (camel_message_info_uid (*info0), NULL, 10);
+	uid1 = strtoul (camel_message_info_uid (*info1), NULL, 10);
+	
+	if (uid0 == uid1)
+		return 0;
+	
+	return uid0 < uid1 ? -1 : 1;
+}
+
+static void
+imap4_transfer_messages_to (CamelFolder *src, GPtrArray *uids, CamelFolder *dest,
+			    GPtrArray **transferred_uids, gboolean move, CamelException *ex)
+{
+	CamelIMAP4Engine *engine = ((CamelIMAP4Store *) src->parent_store)->engine;
+	CamelOfflineStore *offline = (CamelOfflineStore *) src->parent_store;
+	int i, j, n, id, dest_namelen;
+	CamelMessageInfo *info;
+	CamelIMAP4Command *ic;
+	CamelException lex;
+	GPtrArray *infos;
+	char *set;
+	
+	if (transferred_uids)
+		*transferred_uids = NULL;
+	
+	camel_exception_init (&lex);
+	imap4_sync (src, FALSE, &lex);
+	if (camel_exception_is_set (&lex)) {
+		camel_exception_xfer (ex, &lex);
+		return;
+	}
+	
+	infos = g_ptr_array_new ();
+	for (i = 0; i < uids->len; i++) {
+		if (!(info = camel_folder_summary_uid (src->summary, uids->pdata[i])))
+			continue;
+		
+		g_ptr_array_add (infos, info);
+	}
+	
+	if (infos->len == 0) {
+		g_ptr_array_free (infos, TRUE);
+		return;
+	}
+	
+	g_ptr_array_sort (infos, (GCompareFunc) info_uid_sort);
+	
+	CAMEL_SERVICE_REC_LOCK (src->parent_store, connect_lock);
+	
+	/* check for offline operation */
+	if (offline->state == CAMEL_OFFLINE_STORE_NETWORK_UNAVAIL) {
+		CamelIMAP4Journal *journal = (CamelIMAP4Journal *) ((CamelIMAP4Folder *) dest)->journal;
+		CamelMimeMessage *message;
+		
+		for (i = 0; i < infos->len; i++) {
+			info = infos->pdata[i];
+			
+			if (!(message = imap4_get_message (src, camel_message_info_uid (info), ex)))
+				break;
+			
+			camel_imap4_journal_append (journal, message, info, NULL, ex);
+			camel_object_unref (message);
+			
+			if (camel_exception_is_set (ex))
+				break;
+			
+			if (move)
+				camel_folder_set_message_flags (src, camel_message_info_uid (info),
+								CAMEL_MESSAGE_DELETED, CAMEL_MESSAGE_DELETED);
+		}
+		
+		goto done;
+	}
+	
+	dest_namelen = strlen (camel_imap4_folder_utf7_name ((CamelIMAP4Folder *) dest));
+	
+	for (i = 0; i < infos->len; i += n) {
+		n = camel_imap4_get_uid_set (engine, src->summary, infos, i, 10 + dest_namelen, &set);
+		
+		if (move && (engine->capa & CAMEL_IMAP4_CAPABILITY_XGWMOVE))
+			ic = camel_imap4_engine_queue (engine, src, "UID XGWMOVE %s %F\r\n", set, dest);
+		else
+			ic = camel_imap4_engine_queue (engine, src, "UID COPY %s %F\r\n", set, dest);
+		
+		while ((id = camel_imap4_engine_iterate (engine)) < ic->id && id != -1)
+			;
+		
+		g_free (set);
+		
+		if (id == -1 || ic->status != CAMEL_IMAP4_COMMAND_COMPLETE) {
+			camel_exception_xfer (ex, &ic->ex);
+			camel_imap4_command_unref (ic);
+			g_free (set);
+			goto done;
+		}
+		
+		switch (ic->result) {
+		case CAMEL_IMAP4_RESULT_NO:
+			/* FIXME: would be good to save the NO reason into the err message */
+			if (move) {
+				camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+						      _("Cannot move messages from folder `%s' to folder `%s': Unknown"),
+						      src->full_name, dest->full_name);
+			} else {
+				camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+						      _("Cannot copy messages from folder `%s' to folder `%s': Unknown"),
+						      src->full_name, dest->full_name);
+			}
+			
+			goto done;
+		case CAMEL_IMAP4_RESULT_BAD:
+			if (move) {
+				camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+						      _("Cannot move messages from folder `%s' to folder `%s': Bad command"),
+						      src->full_name, dest->full_name);
+			} else {
+				camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+						      _("Cannot copy messages from folder `%s' to folder `%s': Bad command"),
+						      src->full_name, dest->full_name);
+			}
+			
+			goto done;
+		}
+		
+		camel_imap4_command_unref (ic);
+		
+		if (move && !(engine->capa & CAMEL_IMAP4_CAPABILITY_XGWMOVE)) {
+			for (j = i; j < n; j++) {
+				info = infos->pdata[j];
+				camel_folder_set_message_flags (src, camel_message_info_uid (info),
+								CAMEL_MESSAGE_DELETED, CAMEL_MESSAGE_DELETED);
+			}
+			
+			camel_folder_summary_touch (src->summary);
+		}
+	}
+	
+ done:
+	
+	for (i = 0; i < infos->len; i++)
+		camel_message_info_free (infos->pdata[i]);
+	g_ptr_array_free (infos, TRUE);
+	
+	CAMEL_SERVICE_REC_UNLOCK (src->parent_store, connect_lock);
+}
+
+static GPtrArray *
+imap4_search_by_expression (CamelFolder *folder, const char *expr, CamelException *ex)
+{
+	CamelIMAP4Folder *imap4_folder = (CamelIMAP4Folder *) folder;
+	GPtrArray *matches;
+	
+	CAMEL_SERVICE_REC_LOCK(folder->parent_store, connect_lock);
+	
+	camel_folder_search_set_folder (imap4_folder->search, folder);
+	matches = camel_folder_search_search (imap4_folder->search, expr, NULL, ex);
+	
+	CAMEL_SERVICE_REC_UNLOCK(folder->parent_store, connect_lock);
+	
+	return matches;
+}
+
+static GPtrArray *
+imap4_search_by_uids (CamelFolder *folder, const char *expr, GPtrArray *uids, CamelException *ex)
+{
+	CamelIMAP4Folder *imap4_folder = (CamelIMAP4Folder *) folder;
+	GPtrArray *matches;
+	
+	if (uids->len == 0)
+		return g_ptr_array_new ();
+	
+	CAMEL_SERVICE_REC_LOCK(folder->parent_store, connect_lock);
+	
+	camel_folder_search_set_folder (imap4_folder->search, folder);
+	matches = camel_folder_search_search (imap4_folder->search, expr, uids, ex);
+	
+	CAMEL_SERVICE_REC_UNLOCK(folder->parent_store, connect_lock);
+	
+	return matches;
+}
+
+static void
+imap4_search_free (CamelFolder *folder, GPtrArray *uids)
+{
+	CamelIMAP4Folder *imap4_folder = (CamelIMAP4Folder *) folder;
+	
+	g_return_if_fail (imap4_folder->search);
+	
+	CAMEL_SERVICE_REC_LOCK(folder->parent_store, connect_lock);
+	
+	camel_folder_search_free_result (imap4_folder->search, uids);
+	
+	CAMEL_SERVICE_REC_UNLOCK(folder->parent_store, connect_lock);
+}

Added: trunk/imap4/camel-imap4-folder.h
==============================================================================
--- (empty file)
+++ trunk/imap4/camel-imap4-folder.h	Sat May 10 17:53:05 2008
@@ -0,0 +1,84 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*  Camel
+ *  Copyright (C) 1999-2007 Novell, Inc. (www.novell.com)
+ *
+ *  Authors: Jeffrey Stedfast <fejj novell com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU Lesser General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  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 Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+
+#ifndef __CAMEL_IMAP4_FOLDER_H__
+#define __CAMEL_IMAP4_FOLDER_H__
+
+#include <camel/camel-store.h>
+#include <camel/camel-folder.h>
+#include <camel/camel-data-cache.h>
+#include <camel/camel-offline-folder.h>
+#include <camel/camel-offline-journal.h>
+
+#define CAMEL_TYPE_IMAP4_FOLDER            (camel_imap4_folder_get_type ())
+#define CAMEL_IMAP4_FOLDER(obj)            (CAMEL_CHECK_CAST ((obj), CAMEL_TYPE_IMAP4_FOLDER, CamelIMAP4Folder))
+#define CAMEL_IMAP4_FOLDER_CLASS(klass)    (CAMEL_CHECK_CLASS_CAST ((klass), CAMEL_TYPE_IMAP4_FOLDER, CamelIMAP4FolderClass))
+#define CAMEL_IS_IMAP4_FOLDER(obj)         (CAMEL_CHECK_TYPE ((obj), CAMEL_TYPE_IMAP4_FOLDER))
+#define CAMEL_IS_IMAP4_FOLDER_CLASS(klass) (CAMEL_CHECK_CLASS_TYPE ((klass), CAMEL_TYPE_IMAP4_FOLDER))
+#define CAMEL_IMAP4_FOLDER_GET_CLASS(obj)  (CAMEL_CHECK_GET_CLASS ((obj), CAMEL_TYPE_IMAP4_FOLDER, CamelIMAP4FolderClass))
+
+G_BEGIN_DECLS
+
+typedef struct _CamelIMAP4Folder CamelIMAP4Folder;
+typedef struct _CamelIMAP4FolderClass CamelIMAP4FolderClass;
+
+struct _CamelIMAP4Journal;
+
+enum {
+	CAMEL_IMAP4_FOLDER_ARG_ENABLE_MLIST = CAMEL_OFFLINE_FOLDER_ARG_LAST,
+	CAMEL_IMAP4_FOLDER_ARG_LAST = CAMEL_OFFLINE_FOLDER_ARG_LAST + 0x100
+};
+
+enum {
+	CAMEL_IMAP4_FOLDER_ENABLE_MLIST = CAMEL_IMAP4_FOLDER_ARG_ENABLE_MLIST | CAMEL_ARG_BOO,
+};
+
+struct _CamelIMAP4Folder {
+	CamelOfflineFolder parent_object;
+	
+	CamelFolderSearch *search;
+	
+	CamelOfflineJournal *journal;
+	CamelDataCache *cache;
+	
+	char *cachedir;
+	char *utf7_name;
+	
+	unsigned int read_only:1;
+	unsigned int enable_mlist:1;
+};
+
+struct _CamelIMAP4FolderClass {
+	CamelOfflineFolderClass parent_class;
+	
+};
+
+
+CamelType camel_imap4_folder_get_type (void);
+
+CamelFolder *camel_imap4_folder_new (CamelStore *store, const char *full_name, CamelException *ex);
+
+const char *camel_imap4_folder_utf7_name (CamelIMAP4Folder *folder);
+
+G_END_DECLS
+
+#endif /* __CAMEL_IMAP4_FOLDER_H__ */

Added: trunk/imap4/camel-imap4-journal.c
==============================================================================
--- (empty file)
+++ trunk/imap4/camel-imap4-journal.c	Sat May 10 17:53:05 2008
@@ -0,0 +1,362 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*  Camel
+ *  Copyright (C) 1999-2007 Novell, Inc. (www.novell.com)
+ *
+ *  Authors: Jeffrey Stedfast <fejj novell com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU Lesser General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  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 Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <ctype.h>
+
+#include <glib/gi18n-lib.h>
+
+#include <camel/camel-folder-summary.h>
+#include <camel/camel-data-cache.h>
+#include <camel/camel-file-utils.h>
+#include <camel/camel-folder.h>
+
+#include "camel-imap4-folder.h"
+#include "camel-imap4-journal.h"
+
+#define d(x) x
+
+
+static void camel_imap4_journal_class_init (CamelIMAP4JournalClass *klass);
+static void camel_imap4_journal_init (CamelIMAP4Journal *journal, CamelIMAP4JournalClass *klass);
+static void camel_imap4_journal_finalize (CamelObject *object);
+
+static void imap4_entry_free (CamelOfflineJournal *journal, EDListNode *entry);
+static EDListNode *imap4_entry_load (CamelOfflineJournal *journal, FILE *in);
+static int imap4_entry_write (CamelOfflineJournal *journal, EDListNode *entry, FILE *out);
+static int imap4_entry_play (CamelOfflineJournal *journal, EDListNode *entry, CamelException *ex);
+
+
+static CamelOfflineJournalClass *parent_class = NULL;
+
+
+CamelType
+camel_imap4_journal_get_type (void)
+{
+	static CamelType type = 0;
+	
+	if (!type) {
+		type = camel_type_register (camel_offline_journal_get_type (),
+					    "CamelIMAP4Journal",
+					    sizeof (CamelIMAP4Journal),
+					    sizeof (CamelIMAP4JournalClass),
+					    (CamelObjectClassInitFunc) camel_imap4_journal_class_init,
+					    NULL,
+					    (CamelObjectInitFunc) camel_imap4_journal_init,
+					    (CamelObjectFinalizeFunc) camel_imap4_journal_finalize);
+	}
+	
+	return type;
+}
+
+static void
+camel_imap4_journal_class_init (CamelIMAP4JournalClass *klass)
+{
+	CamelOfflineJournalClass *journal_class = (CamelOfflineJournalClass *) klass;
+	
+	parent_class = (CamelOfflineJournalClass *) camel_type_get_global_classfuncs (CAMEL_TYPE_OFFLINE_JOURNAL);
+	
+	journal_class->entry_free = imap4_entry_free;
+	journal_class->entry_load = imap4_entry_load;
+	journal_class->entry_write = imap4_entry_write;
+	journal_class->entry_play = imap4_entry_play;
+}
+
+static void
+camel_imap4_journal_init (CamelIMAP4Journal *journal, CamelIMAP4JournalClass *klass)
+{
+	journal->failed = g_ptr_array_new ();
+}
+
+static void
+camel_imap4_journal_finalize (CamelObject *object)
+{
+	CamelIMAP4Journal *journal = (CamelIMAP4Journal *) object;
+	int i;
+	
+	if (journal->failed) {
+		for (i = 0; i < journal->failed->len; i++)
+			camel_message_info_free (journal->failed->pdata[i]);
+		g_ptr_array_free (journal->failed, TRUE);
+	}
+}
+
+static void
+imap4_entry_free (CamelOfflineJournal *journal, EDListNode *entry)
+{
+	CamelIMAP4JournalEntry *imap4_entry = (CamelIMAP4JournalEntry *) entry;
+	
+	g_free (imap4_entry->v.append_uid);
+	g_free (imap4_entry);
+}
+
+static EDListNode *
+imap4_entry_load (CamelOfflineJournal *journal, FILE *in)
+{
+	CamelIMAP4JournalEntry *entry;
+	
+	entry = g_malloc0 (sizeof (CamelIMAP4JournalEntry));
+	
+	if (camel_file_util_decode_uint32 (in, &entry->type) == -1)
+		goto exception;
+	
+	switch (entry->type) {
+	case CAMEL_IMAP4_JOURNAL_ENTRY_APPEND:
+		if (camel_file_util_decode_string (in, &entry->v.append_uid) == -1)
+			goto exception;
+		
+		break;
+	default:
+		goto exception;
+	}
+	
+	return (EDListNode *) entry;
+	
+ exception:
+	
+	switch (entry->type) {
+	case CAMEL_IMAP4_JOURNAL_ENTRY_APPEND:
+		g_free (entry->v.append_uid);
+		break;
+	default:
+		g_assert_not_reached ();
+	}
+	
+	g_free (entry);
+	
+	return NULL;
+}
+
+static int
+imap4_entry_write (CamelOfflineJournal *journal, EDListNode *entry, FILE *out)
+{
+	CamelIMAP4JournalEntry *imap4_entry = (CamelIMAP4JournalEntry *) entry;
+	
+	if (camel_file_util_encode_uint32 (out, imap4_entry->type) == -1)
+		return -1;
+	
+	switch (imap4_entry->type) {
+	case CAMEL_IMAP4_JOURNAL_ENTRY_APPEND:
+		if (camel_file_util_encode_string (out, imap4_entry->v.append_uid))
+			return -1;
+		
+		break;
+	default:
+		g_assert_not_reached ();
+	}
+	
+	return 0;
+}
+
+static void
+imap4_message_info_dup_to (CamelMessageInfoBase *dest, CamelMessageInfoBase *src)
+{
+	camel_flag_list_copy (&dest->user_flags, &src->user_flags);
+	camel_tag_list_copy (&dest->user_tags, &src->user_tags);
+	dest->date_received = src->date_received;
+	dest->date_sent = src->date_sent;
+	dest->flags = src->flags;
+	dest->size = src->size;
+}
+
+static int
+imap4_entry_play_append (CamelOfflineJournal *journal, CamelIMAP4JournalEntry *entry, CamelException *ex)
+{
+	CamelIMAP4Folder *imap4_folder = (CamelIMAP4Folder *) journal->folder;
+	CamelFolder *folder = journal->folder;
+	CamelMessageInfo *info, *real;
+	CamelMimeMessage *message;
+	CamelStream *stream;
+	CamelException lex;
+	char *uid = NULL;
+	
+	/* if the message isn't in the cache, the user went behind our backs so "not our problem" */
+	if (!imap4_folder->cache || !(stream = camel_data_cache_get (imap4_folder->cache, "cache", entry->v.append_uid, ex)))
+		goto done;
+	
+	message = camel_mime_message_new ();
+	if (camel_data_wrapper_construct_from_stream ((CamelDataWrapper *) message, stream) == -1) {
+		camel_object_unref (message);
+		camel_object_unref (stream);
+		goto done;
+	}
+	
+	camel_object_unref (stream);
+	
+	if (!(info = camel_folder_summary_uid (folder->summary, entry->v.append_uid))) {
+		/* info not in the summary, either because the summary
+		 * got corrupted or because the previous time this
+		 * journal was replay'd, it failed [1] */
+		info = camel_message_info_new (NULL);
+	}
+	
+	camel_exception_init (&lex);
+	camel_folder_append_message (folder, message, info, &uid, &lex);
+	camel_object_unref (message);
+	
+	if (camel_exception_is_set (&lex)) {
+		/* Remove the message-info from the summary even if we fail or the next
+		 * summary downsync will break because info indexes will be wrong */
+		if (info->summary == folder->summary) {
+			camel_folder_summary_remove (folder->summary, info);
+			g_ptr_array_add (((CamelIMAP4Journal *) journal)->failed, info);
+		} else {
+			/* info wasn't in the summary to begin with */
+			camel_folder_summary_remove_uid (folder->summary, entry->v.append_uid);
+			camel_message_info_free (info);
+		}
+		
+		camel_exception_xfer (ex, &lex);
+		
+		return -1;
+	}
+	
+	if (uid != NULL && (real = camel_folder_summary_uid (folder->summary, uid))) {
+		/* Copy the system flags and user flags/tags over to the real
+		   message-info now stored in the summary to prevent the user
+		   from losing any of this meta-data */
+		imap4_message_info_dup_to ((CamelMessageInfoBase *) real, (CamelMessageInfoBase *) info);
+	}
+	
+	camel_message_info_free (info);
+	g_free (uid);
+	
+ done:
+	
+	camel_folder_summary_remove_uid (folder->summary, entry->v.append_uid);
+	camel_data_cache_remove (imap4_folder->cache, "cache", entry->v.append_uid, NULL);
+	
+	return 0;
+}
+
+static int
+imap4_entry_play (CamelOfflineJournal *journal, EDListNode *entry, CamelException *ex)
+{
+	CamelIMAP4JournalEntry *imap4_entry = (CamelIMAP4JournalEntry *) entry;
+	
+	switch (imap4_entry->type) {
+	case CAMEL_IMAP4_JOURNAL_ENTRY_APPEND:
+		return imap4_entry_play_append (journal, imap4_entry, ex);
+	default:
+		g_assert_not_reached ();
+		return -1;
+	}
+}
+
+
+
+CamelOfflineJournal *
+camel_imap4_journal_new (CamelIMAP4Folder *folder, const char *filename)
+{
+	CamelOfflineJournal *journal;
+	
+	g_return_val_if_fail (CAMEL_IS_IMAP4_FOLDER (folder), NULL);
+	
+	journal = (CamelOfflineJournal *) camel_object_new (camel_imap4_journal_get_type ());
+	camel_offline_journal_construct (journal, (CamelFolder *) folder, filename);
+	
+	return journal;
+}
+
+
+void
+camel_imap4_journal_readd_failed (CamelIMAP4Journal *journal)
+{
+	CamelFolderSummary *summary = ((CamelOfflineJournal *) journal)->folder->summary;
+	int i;
+	
+	for (i = 0; i < journal->failed->len; i++)
+		camel_folder_summary_add (summary, journal->failed->pdata[i]);
+	
+	g_ptr_array_set_size (journal->failed, 0);
+}
+
+
+void
+camel_imap4_journal_append (CamelIMAP4Journal *imap4_journal, CamelMimeMessage *message,
+			    const CamelMessageInfo *mi, char **appended_uid, CamelException *ex)
+{
+	CamelOfflineJournal *journal = (CamelOfflineJournal *) imap4_journal;
+	CamelIMAP4Folder *imap4_folder = (CamelIMAP4Folder *) journal->folder;
+	CamelFolder *folder = (CamelFolder *) journal->folder;
+	CamelIMAP4JournalEntry *entry;
+	CamelMessageInfo *info;
+	CamelStream *cache;
+	guint32 nextuid;
+	char *uid;
+	
+	if (imap4_folder->cache == NULL) {
+		camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM,
+				     _("Cannot append message in offline mode: cache unavailable"));
+		return;
+	}
+	
+	nextuid = camel_folder_summary_next_uid (folder->summary);
+	uid = g_strdup_printf ("-%u", nextuid);
+	
+	if (!(cache = camel_data_cache_add (imap4_folder->cache, "cache", uid, ex))) {
+		folder->summary->nextuid--;
+		g_free (uid);
+		return;
+	}
+	
+	if (camel_data_wrapper_write_to_stream ((CamelDataWrapper *) message, cache) == -1
+	    || camel_stream_flush (cache) == -1) {
+		camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+				      _("Cannot append message in offline mode: %s"),
+				      g_strerror (errno));
+		camel_data_cache_remove (imap4_folder->cache, "cache", uid, NULL);
+		folder->summary->nextuid--;
+		camel_object_unref (cache);
+		g_free (uid);
+		return;
+	}
+	
+	camel_object_unref (cache);
+	
+	entry = g_new (CamelIMAP4JournalEntry, 1);
+	entry->type = CAMEL_IMAP4_JOURNAL_ENTRY_APPEND;
+	entry->v.append_uid = uid;
+	
+	e_dlist_addtail (&journal->queue, (EDListNode *) entry);
+	
+	info = camel_folder_summary_info_new_from_message (folder->summary, message);
+	g_free(info->uid);
+	info->uid = g_strdup (uid);
+	
+	imap4_message_info_dup_to ((CamelMessageInfoBase *) info, (CamelMessageInfoBase *) mi);
+	
+	camel_folder_summary_add (folder->summary, info);
+	
+	if (appended_uid)
+		*appended_uid = g_strdup (uid);
+}

Added: trunk/imap4/camel-imap4-journal.h
==============================================================================
--- (empty file)
+++ trunk/imap4/camel-imap4-journal.h	Sat May 10 17:53:05 2008
@@ -0,0 +1,86 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*  Camel
+ *  Copyright (C) 1999-2007 Novell, Inc. (www.novell.com)
+ *
+ *  Authors: Jeffrey Stedfast <fejj novell com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU Lesser General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  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 Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+
+#ifndef __CAMEL_IMAP4_JOURNAL_H__
+#define __CAMEL_IMAP4_JOURNAL_H__
+
+#include <stdarg.h>
+
+#include <glib.h>
+
+#include <camel/camel-offline-journal.h>
+#include <camel/camel-mime-message.h>
+
+#define CAMEL_TYPE_IMAP4_JOURNAL            (camel_imap4_journal_get_type ())
+#define CAMEL_IMAP4_JOURNAL(obj)            (CAMEL_CHECK_CAST ((obj), CAMEL_TYPE_IMAP4_JOURNAL, CamelIMAP4Journal))
+#define CAMEL_IMAP4_JOURNAL_CLASS(klass)    (CAMEL_CHECK_CLASS_CAST ((klass), CAMEL_TYPE_IMAP4_JOURNAL, CamelIMAP4JournalClass))
+#define CAMEL_IS_IMAP4_JOURNAL(obj)         (CAMEL_CHECK_TYPE ((obj), CAMEL_TYPE_IMAP4_JOURNAL))
+#define CAMEL_IS_IMAP4_JOURNAL_CLASS(klass) (CAMEL_CHECK_CLASS_TYPE ((klass), CAMEL_TYPE_IMAP4_JOURNAL))
+#define CAMEL_IMAP4_JOURNAL_GET_CLASS(obj)  (CAMEL_CHECK_GET_CLASS ((obj), CAMEL_TYPE_IMAP4_JOURNAL, CamelIMAP4JournalClass))
+
+G_BEGIN_DECLS
+
+typedef struct _CamelIMAP4Journal CamelIMAP4Journal;
+typedef struct _CamelIMAP4JournalClass CamelIMAP4JournalClass;
+typedef struct _CamelIMAP4JournalEntry CamelIMAP4JournalEntry;
+
+struct _CamelIMAP4Folder;
+
+enum {
+	CAMEL_IMAP4_JOURNAL_ENTRY_APPEND,
+};
+
+struct _CamelIMAP4JournalEntry {
+	EDListNode node;
+	
+	int type;
+	
+	union {
+		char *append_uid;
+	} v;
+};
+
+struct _CamelIMAP4Journal {
+	CamelOfflineJournal parent_object;
+	
+	GPtrArray *failed;
+};
+
+struct _CamelIMAP4JournalClass {
+	CamelOfflineJournalClass parent_class;
+	
+};
+
+
+CamelType camel_imap4_journal_get_type (void);
+
+CamelOfflineJournal *camel_imap4_journal_new (struct _CamelIMAP4Folder *folder, const char *filename);
+
+void camel_imap4_journal_readd_failed (CamelIMAP4Journal *journal);
+
+/* interfaces for adding a journal entry */
+void camel_imap4_journal_append (CamelIMAP4Journal *journal, CamelMimeMessage *message, const CamelMessageInfo *mi,
+				 char **appended_uid, CamelException *ex);
+
+G_END_DECLS
+
+#endif /* __CAMEL_IMAP4_JOURNAL_H__ */

Added: trunk/imap4/camel-imap4-provider.c
==============================================================================
--- (empty file)
+++ trunk/imap4/camel-imap4-provider.c	Sat May 10 17:53:05 2008
@@ -0,0 +1,154 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*  Camel
+ *  Copyright (C) 1999-2007 Novell, Inc. (www.novell.com)
+ *
+ *  Authors: Jeffrey Stedfast <fejj novell com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU Lesser General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  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 Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+
+#include <glib/gi18n-lib.h>
+
+#include <camel/camel-provider.h>
+#include <camel/camel-sasl.h>
+
+#include "camel-imap4-store.h"
+
+static CamelProviderConfEntry imap4_conf_entries[] = {
+	{ CAMEL_PROVIDER_CONF_SECTION_START, "mailcheck", NULL,
+	  N_("Checking for new mail") },
+	{ CAMEL_PROVIDER_CONF_CHECKBOX, "check_all", NULL,
+	  N_("C_heck for new messages in all folders"), "1" },
+	{ CAMEL_PROVIDER_CONF_SECTION_END },
+	{ CAMEL_PROVIDER_CONF_SECTION_START, "folders", NULL,
+	  N_("Folders") },
+	{ CAMEL_PROVIDER_CONF_CHECKBOX, "use_lsub", NULL,
+	  N_("_Show only subscribed folders"), "1" },
+	{ CAMEL_PROVIDER_CONF_CHECKBOX, "override_namespace", NULL,
+	  N_("O_verride server-supplied folder namespace"), "0" },
+	{ CAMEL_PROVIDER_CONF_ENTRY, "namespace", "override_namespace",
+	  N_("Namespace:") },
+	{ CAMEL_PROVIDER_CONF_SECTION_END },
+	{ CAMEL_PROVIDER_CONF_SECTION_START, "general", NULL, N_("Options") },
+	{ CAMEL_PROVIDER_CONF_CHECKBOX, "filter", NULL,
+	  N_("_Apply filters to new messages in INBOX on this server"), "0" },
+	{ CAMEL_PROVIDER_CONF_CHECKBOX, "filter_junk", NULL,
+	  N_("Check new messages for _Junk contents"), "0" },
+	{ CAMEL_PROVIDER_CONF_CHECKBOX, "filter_junk_inbox", "filter_junk",
+	  N_("Only check for Junk messa_ges in the INBOX folder"), "0" },
+	{ CAMEL_PROVIDER_CONF_CHECKBOX, "sync_offline", NULL,
+	  N_("Automatically synchroni_ze remote mail locally"), "0" },
+	{ CAMEL_PROVIDER_CONF_SECTION_END },
+	{ CAMEL_PROVIDER_CONF_END }
+};
+
+static CamelProvider imap4_provider = {
+	"imap4",
+	N_("IMAP4rev1"),
+	
+	N_("For reading and storing mail on IMAPv4rev1 servers."),
+	
+	"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 | CAMEL_URL_FRAGMENT_IS_PATH,
+	
+	imap4_conf_entries,
+	
+	/* ... */
+};
+
+CamelServiceAuthType camel_imap4_password_authtype = {
+	N_("Password"),
+	
+	N_("This option will connect to the IMAPv4rev1 server using a "
+	   "plaintext password."),
+	
+	"",
+	TRUE
+};
+
+
+static void
+add_hash (guint *hash, char *s)
+{
+	if (s)
+		*hash ^= g_str_hash(s);
+}
+
+static guint
+imap4_url_hash (gconstpointer key)
+{
+	const CamelURL *u = (CamelURL *)key;
+	guint hash = 0;
+	
+	add_hash (&hash, u->user);
+	add_hash (&hash, u->authmech);
+	add_hash (&hash, u->host);
+	hash ^= u->port;
+	
+	return hash;
+}
+
+static int
+check_equal (char *s1, char *s2)
+{
+	if (s1 == NULL) {
+		if (s2 == NULL)
+			return TRUE;
+		else
+			return FALSE;
+	}
+	
+	if (s2 == NULL)
+		return FALSE;
+	
+	return strcmp (s1, s2) == 0;
+}
+
+static int
+imap4_url_equal (gconstpointer a, gconstpointer b)
+{
+	const CamelURL *u1 = a, *u2 = b;
+	
+	return check_equal (u1->protocol, u2->protocol)
+		&& check_equal (u1->user, u2->user)
+		&& check_equal (u1->authmech, u2->authmech)
+		&& check_equal (u1->host, u2->host)
+		&& u1->port == u2->port;
+}
+
+
+void
+camel_provider_module_init (void)
+{
+	imap4_provider.object_types[CAMEL_PROVIDER_STORE] = camel_imap4_store_get_type ();
+	imap4_provider.url_hash = imap4_url_hash;
+	imap4_provider.url_equal = imap4_url_equal;
+	imap4_provider.authtypes = camel_sasl_authtype_list (FALSE);
+	imap4_provider.authtypes = g_list_prepend (imap4_provider.authtypes, &camel_imap4_password_authtype);
+	imap4_provider.translation_domain = GETTEXT_PACKAGE;
+	
+	camel_provider_register (&imap4_provider);
+}

Added: trunk/imap4/camel-imap4-search.c
==============================================================================
--- (empty file)
+++ trunk/imap4/camel-imap4-search.c	Sat May 10 17:53:05 2008
@@ -0,0 +1,316 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*  Camel
+ *  Copyright (C) 1999-2007 Novell, Inc. (www.novell.com)
+ *
+ *  Authors: Jeffrey Stedfast <fejj novell com>
+ *           Michael Zucchi <notzed novell com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU Lesser General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  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 Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include <ctype.h>
+
+#include <camel/camel-offline-store.h>
+
+#include "camel-imap4-command.h"
+#include "camel-imap4-engine.h"
+#include "camel-imap4-search.h"
+#include "camel-imap4-stream.h"
+#include "camel-imap4-utils.h"
+
+static void camel_imap4_search_class_init (CamelIMAP4SearchClass *klass);
+static void camel_imap4_search_init (CamelIMAP4Search *search, CamelIMAP4SearchClass *klass);
+static void camel_imap4_search_finalize (CamelObject *object);
+
+static ESExpResult *imap4_body_contains (struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFolderSearch *search);
+
+
+static CamelFolderSearchClass *parent_class = NULL;
+
+
+CamelType
+camel_imap4_search_get_type (void)
+{
+	static CamelType type = 0;
+	
+	if (!type) {
+		type = camel_type_register (camel_folder_search_get_type (),
+					    "CamelIMAP4Search",
+					    sizeof (CamelIMAP4Search),
+					    sizeof (CamelIMAP4SearchClass),
+					    (CamelObjectClassInitFunc) camel_imap4_search_class_init,
+					    NULL,
+					    (CamelObjectInitFunc) camel_imap4_search_init,
+					    (CamelObjectFinalizeFunc) camel_imap4_search_finalize);
+	}
+	
+	return type;
+}
+
+static void
+camel_imap4_search_class_init (CamelIMAP4SearchClass *klass)
+{
+	CamelFolderSearchClass *search_class = (CamelFolderSearchClass *) klass;
+	
+	parent_class = (CamelFolderSearchClass *) camel_type_get_global_classfuncs (CAMEL_FOLDER_SEARCH_TYPE);
+	
+	search_class->body_contains = imap4_body_contains;
+}
+
+static void
+camel_imap4_search_init (CamelIMAP4Search *search, CamelIMAP4SearchClass *klass)
+{
+	search->engine = NULL;
+}
+
+static void
+camel_imap4_search_finalize (CamelObject *object)
+{
+	;
+}
+
+
+CamelFolderSearch *
+camel_imap4_search_new (CamelIMAP4Engine *engine, const char *cachedir)
+{
+	CamelIMAP4Search *search;
+	
+	search = (CamelIMAP4Search *) camel_object_new (camel_imap4_search_get_type ());
+	camel_folder_search_construct ((CamelFolderSearch *) search);
+	search->engine = engine;
+	
+	return (CamelFolderSearch *) search;
+}
+
+
+static int
+untagged_search (CamelIMAP4Engine *engine, CamelIMAP4Command *ic, guint32 index, camel_imap4_token_t *token, CamelException *ex)
+{
+	CamelFolderSummary *summary = ((CamelFolder *) engine->folder)->summary;
+	GPtrArray *matches = ic->user_data;
+	CamelMessageInfo *info;
+	char uid[12];
+	
+	while (1) {
+		if (camel_imap4_engine_next_token (engine, token, ex) == -1)
+			return -1;
+		
+		if (token->token == '\n')
+			break;
+		
+		if (token->token != CAMEL_IMAP4_TOKEN_NUMBER || token->v.number == 0)
+			goto unexpected;
+		
+		sprintf (uid, "%u", token->v.number);
+		if ((info = camel_folder_summary_uid (summary, uid))) {
+			g_ptr_array_add (matches, (char *) camel_message_info_uid (info));
+			camel_message_info_free (info);
+		}
+	}
+	
+	return 0;
+	
+ unexpected:
+	
+	camel_imap4_utils_set_unexpected_token_error (ex, engine, token);
+	
+	return -1;
+}
+
+static ESExpResult *
+imap4_body_contains (struct _ESExp *f, int argc, struct _ESExpResult **argv, CamelFolderSearch *search)
+{
+	CamelIMAP4Search *imap4_search = (CamelIMAP4Search *) search;
+	CamelIMAP4Engine *engine = imap4_search->engine;
+	GPtrArray *strings, *matches, *infos;
+	register const unsigned char *inptr;
+	gboolean utf8_search = FALSE;
+	GPtrArray *summary_set;
+	CamelMessageInfo *info;
+	CamelIMAP4Command *ic;
+	const char *expr;
+	ESExpResult *r;
+	int id, i, n;
+	size_t used;
+	char *set;
+	
+	if (((CamelOfflineStore *) engine->service)->state == CAMEL_OFFLINE_STORE_NETWORK_UNAVAIL)
+		return parent_class->body_contains (f, argc, argv, search);
+	
+	summary_set = search->summary_set ? search->summary_set : search->summary;
+	
+	/* check the simple cases */
+	if (argc == 0 || summary_set->len == 0) {
+		/* match nothing */
+		if (search->current) {
+			r = e_sexp_result_new (f, ESEXP_RES_BOOL);
+			r->value.bool = FALSE;
+		} else {
+			r = e_sexp_result_new (f, ESEXP_RES_ARRAY_PTR);
+			r->value.ptrarray = g_ptr_array_new ();
+		}
+		
+		return r;
+	} else if (argc == 1 && argv[0]->type == ESEXP_RES_STRING && argv[0]->value.string[0] == '\0') {
+		/* match everything */
+		if (search->current) {
+			r = e_sexp_result_new (f, ESEXP_RES_BOOL);
+			r->value.bool = TRUE;
+		} else {
+			r = e_sexp_result_new (f, ESEXP_RES_ARRAY_PTR);
+			r->value.ptrarray = g_ptr_array_new ();
+			g_ptr_array_set_size (r->value.ptrarray, summary_set->len);
+			r->value.ptrarray->len = summary_set->len;
+			for (i = 0; i < summary_set->len; i++) {
+				info = g_ptr_array_index (summary_set, i);
+				r->value.ptrarray->pdata[i] = (char *) camel_message_info_uid (info);
+			}
+		}
+		
+		return r;
+	}
+	
+	strings = g_ptr_array_new ();
+	for (i = 0; i < argc; i++) {
+		if (argv[i]->type == ESEXP_RES_STRING && argv[i]->value.string[0] != '\0') {
+			g_ptr_array_add (strings, argv[i]->value.string);
+			if (!utf8_search) {
+				inptr = (unsigned char *) argv[i]->value.string;
+				while (*inptr != '\0') {
+					if (!isascii ((int) *inptr)) {
+						utf8_search = TRUE;
+						break;
+					}
+					
+					inptr++;
+				}
+			}
+		}
+	}
+	
+	if (strings->len == 0) {
+		/* match everything */
+		g_ptr_array_free (strings, TRUE);
+		
+		if (search->current) {
+			r = e_sexp_result_new (f, ESEXP_RES_BOOL);
+			r->value.bool = TRUE;
+		} else {
+			r = e_sexp_result_new (f, ESEXP_RES_ARRAY_PTR);
+			r->value.ptrarray = g_ptr_array_new ();
+			g_ptr_array_set_size (r->value.ptrarray, summary_set->len);
+			r->value.ptrarray->len = summary_set->len;
+			for (i = 0; i < summary_set->len; i++) {
+				info = g_ptr_array_index (summary_set, i);
+				r->value.ptrarray->pdata[i] = (char *) camel_message_info_uid (info);
+			}
+		}
+		
+		return r;
+	}
+	
+	g_ptr_array_add (strings, NULL);
+	matches = g_ptr_array_new ();
+	infos = g_ptr_array_new ();
+	
+	if (search->current) {
+		g_ptr_array_add (infos, search->current);
+	} else {
+		g_ptr_array_set_size (infos, summary_set->len);
+		infos->len = summary_set->len;
+		for (i = 0; i < summary_set->len; i++)
+			infos->pdata[i] = summary_set->pdata[i];
+	}
+	
+ retry:
+	if (utf8_search && (engine->capa & CAMEL_IMAP4_CAPABILITY_utf8_search))
+		expr = "UID SEARCH CHARSET UTF-8 UID %s BODY %V\r\n";
+	else
+		expr = "UID SEARCH UID %s BODY %V\r\n";
+	
+	used = strlen (expr) + (5 * (strings->len - 2));
+	
+	for (i = 0; i < infos->len; i += n) {
+		n = camel_imap4_get_uid_set (engine, search->folder->summary, infos, i, used, &set);
+		
+		ic = camel_imap4_engine_queue (engine, search->folder, expr, set, strings->pdata);
+		camel_imap4_command_register_untagged (ic, "SEARCH", untagged_search);
+		ic->user_data = matches;
+		g_free (set);
+		
+		while ((id = camel_imap4_engine_iterate (engine)) < ic->id && id != -1)
+			;
+		
+		if (id == -1 || ic->status != CAMEL_IMAP4_COMMAND_COMPLETE) {
+			camel_imap4_command_unref (ic);
+			goto done;
+		}
+		
+		
+		if (ic->result == CAMEL_IMAP4_RESULT_NO && utf8_search && (engine->capa & CAMEL_IMAP4_CAPABILITY_utf8_search)) {
+			int j;
+			
+			/* might be because the server is lame and doesn't support UTF-8 */
+			for (j = 0; j < ic->resp_codes->len; j++) {
+				CamelIMAP4RespCode *resp = ic->resp_codes->pdata[j];
+				
+				if (resp->code == CAMEL_IMAP4_RESP_CODE_BADCHARSET) {
+					engine->capa &= ~CAMEL_IMAP4_CAPABILITY_utf8_search;
+					camel_imap4_command_unref (ic);
+					goto retry;
+				}
+			}
+		}
+		
+		if (ic->result != CAMEL_IMAP4_RESULT_OK) {
+			camel_imap4_command_unref (ic);
+			break;
+		}
+		
+		camel_imap4_command_unref (ic);
+	}
+	
+ done:
+	
+	g_ptr_array_free (strings, TRUE);
+	g_ptr_array_free (infos, TRUE);
+	
+	if (search->current) {
+		const char *uid;
+		
+		uid = camel_message_info_uid (search->current);
+		r = e_sexp_result_new (f, ESEXP_RES_BOOL);
+		r->value.bool = FALSE;
+		for (i = 0; i < matches->len; i++) {
+			if (!strcmp (matches->pdata[i], uid)) {
+				r->value.bool = TRUE;
+				break;
+			}
+		}
+		
+		g_ptr_array_free (matches, TRUE);
+	} else {
+		r = e_sexp_result_new (f, ESEXP_RES_ARRAY_PTR);
+		r->value.ptrarray = matches;
+	}
+	
+	return r;
+}

Added: trunk/imap4/camel-imap4-search.h
==============================================================================
--- (empty file)
+++ trunk/imap4/camel-imap4-search.h	Sat May 10 17:53:05 2008
@@ -0,0 +1,72 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*  Camel
+ *  Copyright (C) 1999-2007 Novell, Inc. (www.novell.com)
+ *
+ *  Authors: Jeffrey Stedfast <fejj novell com>
+ *           Michael Zucchi <notzed novell com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU Lesser General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  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 Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+
+#ifndef __CAMEL_IMAP4_SEARCH_H__
+#define __CAMEL_IMAP4_SEARCH_H__
+
+#include <libedataserver/e-msgport.h>
+
+#include <camel/camel-data-cache.h>
+#include <camel/camel-folder-search.h>
+
+#define CAMEL_IMAP4_SEARCH_TYPE         (camel_imap4_search_get_type ())
+#define CAMEL_IMAP4_SEARCH(obj)         CAMEL_CHECK_CAST (obj, camel_imap4_search_get_type (), CamelIMAP4Search)
+#define CAMEL_IMAP4_SEARCH_CLASS(klass) CAMEL_CHECK_CLASS_CAST (klass, camel_imap4_search_get_type (), CamelIMAP4SearchClass)
+#define CAMEL_IS_IMAP4_SEARCH(obj)      CAMEL_CHECK_TYPE (obj, camel_imap4_search_get_type ())
+
+G_BEGIN_DECLS
+
+typedef struct _CamelIMAP4Search CamelIMAP4Search;
+typedef struct _CamelIMAP4SearchClass CamelIMAP4SearchClass;
+
+struct _CamelIMAP4Engine;
+
+struct _CamelIMAP4Search {
+	CamelFolderSearch parent_object;
+	
+	struct _CamelIMAP4Engine *engine;
+	
+	guint32 lastuid;	/* current 'last uid' for the folder */
+	guint32 validity;	/* validity of the current folder */
+	
+	CamelDataCache *cache;	/* disk-cache for searches */
+	
+	/* cache of body search matches */
+	EDList matches;
+	GHashTable *matches_hash;
+	unsigned int matches_count;
+};
+
+struct _CamelIMAP4SearchClass {
+	CamelFolderSearchClass parent_class;
+	
+};
+
+
+CamelType camel_imap4_search_get_type (void);
+
+CamelFolderSearch *camel_imap4_search_new (struct _CamelIMAP4Engine *engine, const char *cachedir);
+
+G_END_DECLS
+
+#endif /* __CAMEL_IMAP4_SEARCH_H__ */

Added: trunk/imap4/camel-imap4-specials.c
==============================================================================
--- (empty file)
+++ trunk/imap4/camel-imap4-specials.c	Sat May 10 17:53:05 2008
@@ -0,0 +1,102 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*  Camel
+ *  Copyright (C) 1999-2007 Novell, Inc. (www.novell.com)
+ *
+ *  Authors: Jeffrey Stedfast <fejj novell com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU Lesser General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  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 Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+
+#include "camel-imap4-specials.h"
+
+#define CHARS_ATOM_SPECIALS   "(){]"
+#define CHARS_LWSP            " \t\r\n"
+#define CHARS_QUOTED_SPECIALS "\\\""
+#define CHARS_LIST_WILDCARDS  "*%"
+
+unsigned char camel_imap4_specials[256] = {
+	  2,  2,  2,  2,  2,  2,  2,  2,  2,  6,  6,  2,  2,  6,  2,  2,
+          2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,
+         20,  0,  8,  0,  0, 32,  0,  0,  1,  1, 32,  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,  8,  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,  1,  0,  0,  0,  2,
+          2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,
+          2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,
+          2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,
+          2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,
+          2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,
+          2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,
+          2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,
+          2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,
+};
+
+
+static void
+imap4_init_bits (unsigned short bit, unsigned short bitcopy, int remove, unsigned char *vals)
+{
+	int i, len = strlen (vals);
+	
+	if (!remove) {
+		for (i = 0; i < len; i++)
+			camel_imap4_specials[vals[i]] |= bit;
+		if (bitcopy) {
+			for (i = 0; i < 256; i++) {
+				if (camel_imap4_specials[i] & bitcopy)
+					camel_imap4_specials[i] |= bit;
+			}
+		}
+	} else {
+		for (i = 0; i < 256; i++)
+			camel_imap4_specials[i] |= bit;
+		for (i = 0; i < len; i++)
+			camel_imap4_specials[vals[i]] &= ~bit;
+		if (bitcopy) {
+			for (i = 0; i < 256; i++) {
+				if (camel_imap4_specials[i] & bitcopy)
+					camel_imap4_specials[i] &= ~bit;
+			}
+		}
+	}
+}
+
+
+void
+camel_imap4_specials_init (void)
+{
+	int i;
+	
+	for (i = 0; i < 256; i++) {
+		camel_imap4_specials[i] = 0;
+		if (i <= 0x1f || i >= 0x7f)
+			camel_imap4_specials[i] |= IS_CTRL;
+	}
+	
+	camel_imap4_specials[' '] |= IS_SPACE;
+	
+	imap4_init_bits (IS_LWSP, 0, 0, CHARS_LWSP);
+	imap4_init_bits (IS_ASPECIAL, 0, 0, CHARS_ATOM_SPECIALS);
+	imap4_init_bits (IS_QSPECIAL, 0, 0, CHARS_QUOTED_SPECIALS);
+	imap4_init_bits (IS_WILDCARD, 0, 0, CHARS_LIST_WILDCARDS);
+}

Added: trunk/imap4/camel-imap4-specials.h
==============================================================================
--- (empty file)
+++ trunk/imap4/camel-imap4-specials.h	Sat May 10 17:53:05 2008
@@ -0,0 +1,52 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*  Camel
+ *  Copyright (C) 1999-2007 Novell, Inc. (www.novell.com)
+ *
+ *  Authors: Jeffrey Stedfast <fejj novell com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU Lesser General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  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 Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+
+#ifndef __CAMEL_IMAP4_SPECIALS_H__
+#define __CAMEL_IMAP4_SPECIALS_H__
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+enum {
+	IS_ASPECIAL   = (1 << 0),
+	IS_CTRL       = (1 << 1),
+	IS_LWSP       = (1 << 2),
+	IS_QSPECIAL   = (1 << 3),
+	IS_SPACE      = (1 << 4),
+	IS_WILDCARD   = (1 << 5),
+};
+
+extern unsigned char camel_imap4_specials[256];
+
+#define is_atom(x) ((camel_imap4_specials[(unsigned char)(x)] & (IS_ASPECIAL|IS_SPACE|IS_CTRL|IS_WILDCARD|IS_QSPECIAL)) == 0)
+#define is_ctrl(x) ((camel_imap4_specials[(unsigned char)(x)] & IS_CTRL) != 0)
+#define is_lwsp(x) ((camel_imap4_specials[(unsigned char)(x)] & IS_LWSP) != 0)
+#define is_type(x, t) ((camel_imap4_specials[(unsigned char)(x)] & (t)) != 0)
+#define is_qsafe(x) ((camel_imap4_specials[(unsigned char)(x)] & (IS_QSPECIAL|IS_CTRL)) == 0)
+#define is_wild(x)  ((camel_imap4_specials[(unsigned char)(x)] & IS_WILDCARD) != 0)
+
+void camel_imap4_specials_init (void);
+
+G_END_DECLS
+
+#endif /* __CAMEL_IMAP4_SPECIALS_H__ */

Added: trunk/imap4/camel-imap4-store-summary.c
==============================================================================
--- (empty file)
+++ trunk/imap4/camel-imap4-store-summary.c	Sat May 10 17:53:05 2008
@@ -0,0 +1,411 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*  Camel
+ *  Copyright (C) 1999-2007 Novell, Inc. (www.novell.com)
+ *
+ *  Authors: Jeffrey Stedfast <fejj novell com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU Lesser General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  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 Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+
+#include <glib/gi18n-lib.h>
+
+#include <camel/camel-file-utils.h>
+#include <camel/camel-store.h>
+
+#include "camel-imap4-store-summary.h"
+#include "camel-imap4-utils.h"
+
+#define CAMEL_IMAP4_STORE_SUMMARY_VERSION_0 (0)
+#define CAMEL_IMAP4_STORE_SUMMARY_VERSION (0)
+
+static void camel_imap4_store_summary_class_init (CamelIMAP4StoreSummaryClass *klass);
+static void camel_imap4_store_summary_init (CamelIMAP4StoreSummary *obj);
+static void camel_imap4_store_summary_finalize (CamelObject *obj);
+
+static int summary_header_load (CamelStoreSummary *s, FILE *in);
+static int summary_header_save (CamelStoreSummary *s, FILE *out);
+
+static CamelStoreInfo *store_info_load (CamelStoreSummary *s, FILE *in);
+static int store_info_save (CamelStoreSummary *s, FILE *out, CamelStoreInfo *info);
+static void store_info_free (CamelStoreSummary *s, CamelStoreInfo *info);
+
+
+static CamelStoreSummaryClass *parent_class = NULL;
+
+
+CamelType
+camel_imap4_store_summary_get_type (void)
+{
+	static CamelType type = CAMEL_INVALID_TYPE;
+	
+	if (type == CAMEL_INVALID_TYPE) {
+		type = camel_type_register (camel_store_summary_get_type (),
+					    "CamelIMAP4StoreSummary",
+					    sizeof (CamelIMAP4StoreSummary),
+					    sizeof (CamelIMAP4StoreSummaryClass),
+					    (CamelObjectClassInitFunc) camel_imap4_store_summary_class_init,
+					    NULL,
+					    (CamelObjectInitFunc) camel_imap4_store_summary_init,
+					    (CamelObjectFinalizeFunc) camel_imap4_store_summary_finalize);
+	}
+	
+	return type;
+}
+
+
+static void
+camel_imap4_store_summary_class_init (CamelIMAP4StoreSummaryClass *klass)
+{
+	CamelStoreSummaryClass *ssklass = (CamelStoreSummaryClass *) klass;
+	
+	parent_class = (CamelStoreSummaryClass *) camel_store_summary_get_type ();
+	
+	ssklass->summary_header_load = summary_header_load;
+	ssklass->summary_header_save = summary_header_save;
+	
+	ssklass->store_info_load = store_info_load;
+	ssklass->store_info_save = store_info_save;
+	ssklass->store_info_free = store_info_free;
+}
+
+static void
+camel_imap4_store_summary_init (CamelIMAP4StoreSummary *s)
+{
+	((CamelStoreSummary *) s)->store_info_size = sizeof (CamelIMAP4StoreInfo);
+	s->version = CAMEL_IMAP4_STORE_SUMMARY_VERSION;
+	s->namespaces = NULL;
+}
+
+static void
+camel_imap4_store_summary_finalize (CamelObject *obj)
+{
+	CamelIMAP4StoreSummary *s = (CamelIMAP4StoreSummary *) obj;
+	
+	if (s->namespaces)
+		camel_imap4_namespace_list_free (s->namespaces);
+}
+
+
+static CamelIMAP4NamespaceList *
+load_namespaces (FILE *in)
+{
+	CamelIMAP4Namespace *ns, *tail = NULL;
+	CamelIMAP4NamespaceList *nsl;
+	guint32 i, j, n;
+	
+	nsl = g_malloc (sizeof (CamelIMAP4NamespaceList));
+	nsl->personal = NULL;
+	nsl->shared = NULL;
+	nsl->other = NULL;
+	
+	for (j = 0; j < 3; j++) {
+		switch (j) {
+		case 0:
+			tail = (CamelIMAP4Namespace *) &nsl->personal;
+			break;
+		case 1:
+			tail = (CamelIMAP4Namespace *) &nsl->shared;
+			break;
+		case 2:
+			tail = (CamelIMAP4Namespace *) &nsl->other;
+			break;
+		}
+		
+		if (camel_file_util_decode_fixed_int32 (in, &n) == -1)
+			goto exception;
+		
+		for (i = 0; i < n; i++) {
+			guint32 sep;
+			char *path;
+			
+			if (camel_file_util_decode_string (in, &path) == -1)
+				goto exception;
+			
+			if (camel_file_util_decode_uint32 (in, &sep) == -1) {
+				g_free (path);
+				goto exception;
+			}
+			
+			tail->next = ns = g_malloc (sizeof (CamelIMAP4Namespace));
+			ns->sep = sep & 0xff;
+			ns->path = path;
+			ns->next = NULL;
+			tail = ns;
+		}
+	}
+	
+	return nsl;
+	
+ exception:
+	
+	camel_imap4_namespace_list_free (nsl);
+	
+	return NULL;
+}
+
+static int
+summary_header_load (CamelStoreSummary *s, FILE *in)
+{
+	CamelIMAP4StoreSummary *is = (CamelIMAP4StoreSummary *) s;
+	guint32 version, capa;
+	
+	if (parent_class->summary_header_load (s, in) == -1)
+		return -1;
+	
+	if (camel_file_util_decode_fixed_int32 (in, &version) == -1)
+		return -1;
+	
+	is->version = version;
+	if (version < CAMEL_IMAP4_STORE_SUMMARY_VERSION_0) {
+		g_warning ("IMAP4 store summary header version too low");
+		errno = EINVAL;
+		return -1;
+	}
+	
+	if (camel_file_util_decode_fixed_int32 (in, &capa) == -1)
+		return -1;
+	
+	is->capa = capa;
+	
+	if (!(is->namespaces = load_namespaces (in)))
+		return -1;
+	
+	return 0;
+}
+
+static int
+save_namespaces (FILE *out, CamelIMAP4NamespaceList *nsl)
+{
+	CamelIMAP4Namespace *ns, *cur = NULL;
+	guint32 i, n;
+	
+	for (i = 0; i < 3; i++) {
+		switch (i) {
+		case 0:
+			cur = nsl->personal;
+			break;
+		case 1:
+			cur = nsl->shared;
+			break;
+		case 2:
+			cur = nsl->other;
+			break;
+		}
+		
+		for (ns = cur, n = 0; ns; n++)
+			ns = ns->next;
+		
+		if (camel_file_util_encode_fixed_int32 (out, n) == -1)
+			return -1;
+		
+		ns = cur;
+		while (ns != NULL) {
+			if (camel_file_util_encode_string (out, ns->path) == -1)
+				return -1;
+			
+			if (camel_file_util_encode_uint32 (out, ns->sep) == -1)
+				return -1;
+			
+			ns = ns->next;
+		}
+	}
+	
+	return 0;
+}
+
+static int
+summary_header_save (CamelStoreSummary *s, FILE *out)
+{
+	CamelIMAP4StoreSummary *is = (CamelIMAP4StoreSummary *) s;
+	
+	if (parent_class->summary_header_save (s, out) == -1)
+		return -1;
+	
+	if (camel_file_util_encode_fixed_int32 (out, is->version) == -1)
+		return -1;
+	
+	if (camel_file_util_encode_fixed_int32 (out, is->capa) == -1)
+		return -1;
+	
+	if (save_namespaces (out, is->namespaces) == -1)
+		return -1;
+	
+	return 0;
+}
+
+static CamelStoreInfo *
+store_info_load (CamelStoreSummary *s, FILE *in)
+{
+	return parent_class->store_info_load (s, in);
+}
+
+static int
+store_info_save (CamelStoreSummary *s, FILE *out, CamelStoreInfo *info)
+{
+	return parent_class->store_info_save (s, out, info);
+}
+
+static void
+store_info_free (CamelStoreSummary *s, CamelStoreInfo *info)
+{
+	parent_class->store_info_free (s, info);
+}
+
+
+/**
+ * camel_imap4_store_summary_new:
+ *
+ * Create a new CamelIMAP4StoreSummary object.
+ * 
+ * Returns a new CamelIMAP4StoreSummary object.
+ **/
+CamelIMAP4StoreSummary *
+camel_imap4_store_summary_new (void)
+{
+	return (CamelIMAP4StoreSummary *) camel_object_new (camel_imap4_store_summary_get_type ());
+}
+
+
+void
+camel_imap4_store_summary_set_capabilities (CamelIMAP4StoreSummary *s, guint32 capa)
+{
+	s->capa = capa;
+}
+
+
+void
+camel_imap4_store_summary_set_namespaces (CamelIMAP4StoreSummary *s, const CamelIMAP4NamespaceList *ns)
+{
+	if (s->namespaces)
+		camel_imap4_namespace_list_free (s->namespaces);
+	s->namespaces = camel_imap4_namespace_list_copy (ns);
+}
+
+
+void
+camel_imap4_store_summary_note_info (CamelIMAP4StoreSummary *s, CamelFolderInfo *fi)
+{
+	CamelStoreSummary *ss = (CamelStoreSummary *) s;
+	CamelStoreInfo *si;
+	
+	if ((si = camel_store_summary_path (ss, fi->full_name))) {
+		if (fi->unread != -1) {
+			si->unread = fi->unread;
+			ss->flags |= CAMEL_STORE_SUMMARY_DIRTY;
+		}
+		
+		if (fi->total != -1) {
+			si->total = fi->total;
+			ss->flags |= CAMEL_STORE_SUMMARY_DIRTY;
+		}
+		
+		camel_store_summary_info_free (ss, si);
+		return;
+	}
+	
+	si = camel_store_summary_info_new (ss);
+	si->path = g_strdup (fi->full_name);
+	si->uri = g_strdup (fi->uri);
+	si->flags = fi->flags;
+	si->unread = fi->unread;
+	si->total = fi->total;
+	
+	camel_store_summary_add (ss, si);
+	
+	/* FIXME: should this be recursive? */
+}
+
+
+void
+camel_imap4_store_summary_unnote_info (CamelIMAP4StoreSummary *s, CamelFolderInfo *fi)
+{
+	CamelStoreSummary *ss = (CamelStoreSummary *) s;
+	
+	camel_store_summary_remove_path (ss, fi->full_name);
+}
+
+
+static CamelFolderInfo *
+store_info_to_folder_info (CamelStoreSummary *s, CamelStoreInfo *si)
+{
+	CamelFolderInfo *fi;
+	const char *name;
+
+	fi = camel_folder_info_new ();
+	fi->full_name = g_strdup (camel_store_info_path (s, si));
+	fi->uri = g_strdup (camel_store_info_uri (s, si));
+	fi->flags = si->flags;
+	fi->unread = si->unread;
+	fi->total = si->total;
+
+	name = camel_store_info_name (s, si);
+	if (!g_ascii_strcasecmp (fi->full_name, "INBOX")) {
+		fi->flags |= CAMEL_FOLDER_SYSTEM | CAMEL_FOLDER_TYPE_INBOX;
+		fi->name = g_strdup (_("Inbox"));
+	} else {
+		fi->name = g_strdup (name);
+	}
+	
+	return fi;
+}
+
+CamelFolderInfo *
+camel_imap4_store_summary_get_folder_info (CamelIMAP4StoreSummary *s, const char *top, guint32 flags)
+{
+	CamelStoreSummary *ss = (CamelStoreSummary *) s;
+	CamelFolderInfo *fi;
+	GPtrArray *folders;
+	CamelStoreInfo *si;
+	size_t toplen, len;
+	int i;
+	
+	toplen = strlen (top);
+	folders = g_ptr_array_new ();
+	
+	for (i = 0; i < ss->folders->len; i++) {
+		si = ss->folders->pdata[i];
+		if (strncmp (si->path, top, toplen) != 0)
+			continue;
+		
+		len = strlen (si->path);
+		if (toplen > 0 && len > toplen && si->path[toplen] != '/')
+			continue;
+		
+		if (len == toplen) {
+			/* found toplevel folder */
+			g_ptr_array_add (folders, store_info_to_folder_info (ss, si));
+			continue;
+		}
+		
+		if ((flags & CAMEL_STORE_FOLDER_INFO_RECURSIVE) || !strchr (si->path + toplen + 1, '/'))
+			g_ptr_array_add (folders, store_info_to_folder_info (ss, si));
+	}
+	
+	fi = camel_folder_info_build (folders, top, '/', TRUE);
+	g_ptr_array_free (folders, TRUE);
+	
+	return fi;
+}

Added: trunk/imap4/camel-imap4-store-summary.h
==============================================================================
--- (empty file)
+++ trunk/imap4/camel-imap4-store-summary.h	Sat May 10 17:53:05 2008
@@ -0,0 +1,86 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*  Camel
+ *  Copyright (C) 1999-2007 Novell, Inc. (www.novell.com)
+ *
+ *  Authors: Jeffrey Stedfast <fejj novell com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU Lesser General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  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 Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+
+#ifndef __CAMEL_IMAP_STORE_SUMMARY_H__
+#define __CAMEL_IMAP_STORE_SUMMARY_H__
+
+#include <camel/camel-store-summary.h>
+#include "camel-imap4-engine.h"
+
+#define CAMEL_IMAP4_STORE_SUMMARY(obj)         CAMEL_CHECK_CAST (obj, camel_imap4_store_summary_get_type (), CamelIMAP4StoreSummary)
+#define CAMEL_IMAP4_STORE_SUMMARY_CLASS(klass) CAMEL_CHECK_CLASS_CAST (klass, camel_imap4_store_summary_get_type (), CamelIMAP4StoreSummaryClass)
+#define CAMEL_IS_IMAP4_STORE_SUMMARY(obj)      CAMEL_CHECK_TYPE (obj, camel_imap4_store_summary_get_type ())
+
+G_BEGIN_DECLS
+
+typedef struct _CamelIMAP4StoreSummary      CamelIMAP4StoreSummary;
+typedef struct _CamelIMAP4StoreSummaryClass CamelIMAP4StoreSummaryClass;
+
+typedef struct _CamelIMAP4StoreInfo CamelIMAP4StoreInfo;
+
+enum {
+	CAMEL_IMAP4_STORE_INFO_FULL_NAME = CAMEL_STORE_INFO_LAST,
+	CAMEL_IMAP4_STORE_INFO_LAST,
+};
+
+
+struct _CamelFolderInfo;
+
+
+struct _CamelIMAP4StoreInfo {
+	CamelStoreInfo info;
+};
+
+struct _CamelIMAP4StoreSummary {
+	CamelStoreSummary summary;
+	
+	struct _CamelIMAP4StoreSummaryPrivate *priv;
+	
+	/* header info */
+	guint32 version;
+	
+	CamelIMAP4NamespaceList *namespaces;
+	guint32 capa;
+};
+
+struct _CamelIMAP4StoreSummaryClass {
+	CamelStoreSummaryClass summary_class;
+};
+
+
+CamelType camel_imap4_store_summary_get_type (void);
+
+CamelIMAP4StoreSummary *camel_imap4_store_summary_new (void);
+
+void camel_imap4_store_summary_set_capabilities (CamelIMAP4StoreSummary *s, guint32 capa);
+void camel_imap4_store_summary_set_namespaces (CamelIMAP4StoreSummary *s, const CamelIMAP4NamespaceList *ns);
+
+/* add the info to the cache if we don't already have it, otherwise do nothing */
+void camel_imap4_store_summary_note_info (CamelIMAP4StoreSummary *s, struct _CamelFolderInfo *fi);
+
+void camel_imap4_store_summary_unnote_info (CamelIMAP4StoreSummary *s, struct _CamelFolderInfo *fi);
+
+struct _CamelFolderInfo *camel_imap4_store_summary_get_folder_info (CamelIMAP4StoreSummary *s, const char *top, guint32 flags);
+
+G_END_DECLS
+
+#endif /* __CAMEL_IMAP4_STORE_SUMMARY_H__ */

Added: trunk/imap4/camel-imap4-store.c
==============================================================================
--- (empty file)
+++ trunk/imap4/camel-imap4-store.c	Sat May 10 17:53:05 2008
@@ -0,0 +1,1793 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*  Camel
+ *  Copyright (C) 1999-2007 Novell, Inc. (www.novell.com)
+ *
+ *  Authors: Jeffrey Stedfast <fejj novell com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU Lesser General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  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 Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+#include <errno.h>
+
+#include <glib/gi18n-lib.h>
+
+#include <camel/camel-private.h>
+#include <camel/camel-net-utils.h>
+#include <camel/camel-tcp-stream-raw.h>
+#include <camel/camel-sasl.h>
+#include <camel/camel-utf8.h>
+
+#ifdef HAVE_SSL
+#include <camel/camel-tcp-stream-ssl.h>
+#endif
+
+#include "camel-imap4-command.h"
+#include "camel-imap4-engine.h"
+#include "camel-imap4-folder.h"
+#include "camel-imap4-store-summary.h"
+#include "camel-imap4-store.h"
+#include "camel-imap4-stream.h"
+#include "camel-imap4-summary.h"
+#include "camel-imap4-utils.h"
+
+#define d(x) x
+
+static void camel_imap4_store_class_init (CamelIMAP4StoreClass *klass);
+static void camel_imap4_store_init (CamelIMAP4Store *store, CamelIMAP4StoreClass *klass);
+static void camel_imap4_store_finalize (CamelObject *object);
+
+/* service methods */
+static void imap4_construct (CamelService *service, CamelSession *session,
+			     CamelProvider *provider, CamelURL *url,
+			     CamelException *ex);
+static char *imap4_get_name (CamelService *service, gboolean brief);
+static gboolean imap4_connect (CamelService *service, CamelException *ex);
+static gboolean imap4_reconnect (CamelIMAP4Engine *engine, CamelException *ex);
+static gboolean imap4_disconnect (CamelService *service, gboolean clean, CamelException *ex);
+static GList *imap4_query_auth_types (CamelService *service, CamelException *ex);
+
+/* store methods */
+static CamelFolder *imap4_get_folder (CamelStore *store, const char *folder_name, guint32 flags, CamelException *ex);
+static CamelFolderInfo *imap4_create_folder (CamelStore *store, const char *parent_name, const char *folder_name, CamelException *ex);
+static void imap4_delete_folder (CamelStore *store, const char *folder_name, CamelException *ex);
+static void imap4_rename_folder (CamelStore *store, const char *old_name, const char *new_name, CamelException *ex);
+static CamelFolderInfo *imap4_get_folder_info (CamelStore *store, const char *top, guint32 flags, CamelException *ex);
+static void imap4_subscribe_folder (CamelStore *store, const char *folder_name, CamelException *ex);
+static void imap4_unsubscribe_folder (CamelStore *store, const char *folder_name, CamelException *ex);
+static gboolean imap4_folder_subscribed (CamelStore *store, const char *folder_name);
+static void imap4_noop (CamelStore *store, CamelException *ex);
+
+
+static CamelOfflineStoreClass *parent_class = NULL;
+
+
+CamelType
+camel_imap4_store_get_type (void)
+{
+	static CamelType type = 0;
+	
+	if (!type) {
+		type = camel_type_register (camel_offline_store_get_type (),
+					    "CamelIMAP4Store",
+					    sizeof (CamelIMAP4Store),
+					    sizeof (CamelIMAP4StoreClass),
+					    (CamelObjectClassInitFunc) camel_imap4_store_class_init,
+					    NULL,
+					    (CamelObjectInitFunc) camel_imap4_store_init,
+					    (CamelObjectFinalizeFunc) camel_imap4_store_finalize);
+	}
+	
+	return type;
+}
+
+static guint
+imap4_hash_folder_name (gconstpointer key)
+{
+	if (g_ascii_strcasecmp (key, "INBOX") == 0)
+		return g_str_hash ("INBOX");
+	else
+		return g_str_hash (key);
+}
+
+static int
+imap4_compare_folder_name (gconstpointer a, gconstpointer b)
+{
+	gconstpointer aname = a, bname = b;
+	
+	if (g_ascii_strcasecmp (a, "INBOX") == 0)
+		aname = "INBOX";
+	if (g_ascii_strcasecmp (b, "INBOX") == 0)
+		bname = "INBOX";
+	
+	return g_str_equal (aname, bname);
+}
+
+static void
+camel_imap4_store_class_init (CamelIMAP4StoreClass *klass)
+{
+	CamelServiceClass *service_class = (CamelServiceClass *) klass;
+	CamelStoreClass *store_class = (CamelStoreClass *) klass;
+	
+	parent_class = (CamelOfflineStoreClass *) camel_type_get_global_classfuncs (CAMEL_TYPE_OFFLINE_STORE);
+	
+	service_class->construct = imap4_construct;
+	service_class->get_name = imap4_get_name;
+	service_class->connect = imap4_connect;
+	service_class->disconnect = imap4_disconnect;
+	service_class->query_auth_types = imap4_query_auth_types;
+	
+	store_class->hash_folder_name = imap4_hash_folder_name;
+	store_class->compare_folder_name = imap4_compare_folder_name;
+	
+	store_class->get_folder = imap4_get_folder;
+	store_class->create_folder = imap4_create_folder;
+	store_class->delete_folder = imap4_delete_folder;
+	store_class->rename_folder = imap4_rename_folder;
+	store_class->get_folder_info = imap4_get_folder_info;
+	store_class->subscribe_folder = imap4_subscribe_folder;
+	store_class->unsubscribe_folder = imap4_unsubscribe_folder;
+	store_class->folder_subscribed = imap4_folder_subscribed;
+	store_class->noop = imap4_noop;
+}
+
+static void
+camel_imap4_store_init (CamelIMAP4Store *store, CamelIMAP4StoreClass *klass)
+{
+	store->engine = NULL;
+	store->summary = NULL;
+}
+
+static void
+camel_imap4_store_finalize (CamelObject *object)
+{
+	CamelIMAP4Store *store = (CamelIMAP4Store *) object;
+	
+	if (store->summary) {
+		camel_store_summary_save ((CamelStoreSummary *) store->summary);
+		camel_object_unref (store->summary);
+	}
+	
+	if (store->engine)
+		camel_object_unref (store->engine);
+	
+	g_free (store->storage_path);
+}
+
+
+static void
+imap4_construct (CamelService *service, CamelSession *session, CamelProvider *provider, CamelURL *url, CamelException *ex)
+{
+	CamelIMAP4Store *store = (CamelIMAP4Store *) service;
+	char *buf;
+	
+	CAMEL_SERVICE_CLASS (parent_class)->construct (service, session, provider, url, ex);
+	if (camel_exception_is_set (ex))
+		return;
+	
+	if (camel_url_get_param (url, "use_lsub"))
+		((CamelStore *) store)->flags |= CAMEL_STORE_SUBSCRIPTIONS;
+	
+	store->storage_path = camel_session_get_storage_path (session, service, ex);
+	store->engine = camel_imap4_engine_new (service, imap4_reconnect);
+	
+	/* setup/load the summary */
+	buf = g_alloca (strlen (store->storage_path) + 32);
+	sprintf (buf, "%s/.summary", store->storage_path);
+	store->summary = camel_imap4_store_summary_new ();
+	camel_store_summary_set_filename ((CamelStoreSummary *) store->summary, buf);
+	
+	buf = camel_url_to_string (service->url, CAMEL_URL_HIDE_ALL);
+	url = camel_url_new (buf, NULL);
+	g_free (buf);
+	camel_store_summary_set_uri_base ((CamelStoreSummary *) store->summary, url);
+	camel_url_free (url);
+	
+	camel_store_summary_load ((CamelStoreSummary *) store->summary);
+}
+
+static char *
+imap4_get_name (CamelService *service, gboolean brief)
+{
+	if (brief)
+		return g_strdup_printf (_("IMAP server %s"), service->url->host);
+	else
+		return g_strdup_printf (_("IMAP service for %s on %s"),
+					service->url->user, service->url->host);
+}
+
+enum {
+	MODE_CLEAR,
+	MODE_SSL,
+	MODE_TLS,
+};
+
+#ifdef HAVE_SSL
+#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)
+#endif
+
+static gboolean
+connect_to_server (CamelIMAP4Engine *engine, struct addrinfo *ai, int ssl_mode, CamelException *ex)
+{
+	CamelService *service = engine->service;
+	CamelSockOptData sockopt;
+	CamelStream *tcp_stream;
+#ifdef HAVE_SSL
+	CamelIMAP4Command *ic;
+	int id;
+#endif
+	
+	if (ssl_mode != MODE_CLEAR) {
+#ifdef HAVE_SSL
+		if (ssl_mode == MODE_TLS) {
+			tcp_stream = camel_tcp_stream_ssl_new_raw (service->session, service->url->host, STARTTLS_FLAGS);
+		} else {
+			tcp_stream = camel_tcp_stream_ssl_new (service->session, service->url->host, SSL_PORT_FLAGS);
+		}
+#else
+		camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
+				      _("Could not connect to %s: %s"),
+				      service->url->host, _("SSL unavailable"));
+		
+		return FALSE;
+#endif /* HAVE_SSL */
+	} else {
+		tcp_stream = camel_tcp_stream_raw_new ();
+	}
+	
+	if (camel_tcp_stream_connect ((CamelTcpStream *) tcp_stream, ai) == -1) {
+		if (errno == EINTR)
+			camel_exception_set (ex, CAMEL_EXCEPTION_USER_CANCEL,
+					     _("Connection canceled"));
+		else
+			camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
+					      _("Could not connect to %s: %s"),
+					      service->url->host,
+					      g_strerror (errno));
+		
+		camel_object_unref (tcp_stream);
+		
+		return FALSE;
+	}
+	
+	/* set some socket options to better tailor the connection to our needs */
+	sockopt.option = CAMEL_SOCKOPT_NODELAY;
+	sockopt.value.no_delay = TRUE;
+	camel_tcp_stream_setsockopt ((CamelTcpStream *) tcp_stream, &sockopt);
+	
+	sockopt.option = CAMEL_SOCKOPT_KEEPALIVE;
+	sockopt.value.keep_alive = TRUE;
+	camel_tcp_stream_setsockopt ((CamelTcpStream *) tcp_stream, &sockopt);
+	
+	if (camel_imap4_engine_take_stream (engine, tcp_stream, ex) == -1)
+		return FALSE;
+	
+	if (camel_imap4_engine_capability (engine, ex) == -1)
+		return FALSE;
+	
+	camel_imap4_store_summary_set_capabilities (((CamelIMAP4Store *) service)->summary, engine->capa);
+	
+	if (ssl_mode != MODE_TLS) {
+		/* we're done */
+		return TRUE;
+	}
+	
+#ifdef HAVE_SSL
+	if (!(engine->capa & CAMEL_IMAP4_CAPABILITY_STARTTLS)) {
+		camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+				      _("Failed to connect to IMAP server %s in secure mode: "
+					"Server does not support STARTTLS"),
+				      service->url->host);
+		
+		return FALSE;
+	}
+	
+	ic = camel_imap4_engine_prequeue (engine, NULL, "STARTTLS\r\n");
+	while ((id = camel_imap4_engine_iterate (engine)) < ic->id && id != -1)
+		;
+	
+	if (id == -1 || ic->result != CAMEL_IMAP4_RESULT_OK) {
+		if (ic->result != CAMEL_IMAP4_RESULT_OK) {
+			camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+					      _("Failed to connect to IMAP server %s in secure mode: %s"),
+					      service->url->host, _("Unknown error"));
+		} else {
+			camel_exception_xfer (ex, &ic->ex);
+		}
+		
+		camel_imap4_command_unref (ic);
+		
+		return FALSE;
+	}
+	
+	camel_imap4_command_unref (ic);
+	
+	if (camel_tcp_stream_ssl_enable_ssl ((CamelTcpStreamSSL *) tcp_stream) == -1) {
+		camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+				      _("Failed to connect to IMAP server %s in secure mode: %s"),
+				      service->url->host, _("TLS negotiations failed"));
+		return FALSE;
+	}
+	
+	return TRUE;
+#else
+	camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+			      _("Failed to connect to IMAP server %s in secure mode: %s"),
+			      service->url->host, _("SSL is not available in this build"));
+	
+	return FALSE;
+#endif /* HAVE_SSL */
+}
+
+static struct {
+	char *value;
+	char *serv;
+	char *port;
+	int mode;
+} ssl_options[] = {
+	{ "",              "imaps", "993", MODE_SSL   },  /* really old (1.x) */
+	{ "always",        "imaps", "993", MODE_SSL   },
+	{ "when-possible", "imap",  "143", MODE_TLS   },
+	{ "never",         "imap",  "143", MODE_CLEAR },
+	{ NULL,            "imap",  "143", MODE_CLEAR },
+};
+
+static gboolean
+connect_to_server_wrapper (CamelIMAP4Engine *engine, CamelException *ex)
+{
+	CamelService *service = engine->service;
+	struct addrinfo *ai, hints;
+	const char *ssl_mode;
+	int mode, ret, i;
+	const char *port;
+	char *serv;
+	
+	if ((ssl_mode = camel_url_get_param (service->url, "use_ssl"))) {
+		for (i = 0; ssl_options[i].value; i++)
+			if (!strcmp (ssl_options[i].value, ssl_mode))
+				break;
+		mode = ssl_options[i].mode;
+		serv = ssl_options[i].serv;
+		port = ssl_options[i].port;
+	} else {
+		mode = MODE_CLEAR;
+		serv = "imap";
+		port = "143";
+	}
+	
+	if (service->url->port) {
+		serv = g_alloca (16);
+		sprintf (serv, "%d", service->url->port);
+		port = NULL;
+	}
+	
+	memset (&hints, 0, sizeof (hints));
+	hints.ai_socktype = SOCK_STREAM;
+	hints.ai_family = PF_UNSPEC;
+	ai = camel_getaddrinfo (service->url->host, serv, &hints, ex);
+	if (ai == NULL && port != NULL && camel_exception_get_id(ex) != CAMEL_EXCEPTION_USER_CANCEL) {
+		camel_exception_clear (ex);
+		ai = camel_getaddrinfo (service->url->host, port, &hints, ex);
+	}
+	
+	if (ai == NULL)
+		return FALSE;
+	
+	ret = connect_to_server (engine, ai, mode, ex);
+	
+	camel_freeaddrinfo (ai);
+	
+	return ret;
+}
+
+static int
+sasl_auth (CamelIMAP4Engine *engine, CamelIMAP4Command *ic, const unsigned char *linebuf, size_t linelen, CamelException *ex)
+{
+	/* Perform a single challenge iteration */
+	CamelSasl *sasl = ic->user_data;
+	char *challenge;
+	
+	if (camel_sasl_authenticated (sasl)) {
+		camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE,
+				      _("Cannot authenticate to IMAP server %s using the %s authentication mechanism"),
+				      engine->url->host, engine->url->authmech);
+		return -1;
+	}
+	
+	while (isspace (*linebuf))
+		linebuf++;
+	
+	if (*linebuf == '\0')
+		linebuf = NULL;
+	
+	if (!(challenge = camel_sasl_challenge_base64 (sasl, (const char *) linebuf, ex)))
+		return -1;
+	
+	d(fprintf (stderr, "sending : %s\r\n", challenge));
+	
+	if (camel_stream_printf (engine->ostream, "%s\r\n", challenge) == -1) {
+		g_free (challenge);
+		return -1;
+	}
+	
+	g_free (challenge);
+	
+	if (camel_stream_flush (engine->ostream) == -1)
+		return -1;
+	
+	return 0;
+}
+
+static int
+imap4_try_authenticate (CamelIMAP4Engine *engine, gboolean reprompt, const char *errmsg, CamelException *ex)
+{
+	CamelService *service = engine->service;
+	CamelSession *session = service->session;
+	CamelServiceAuthType *mech = NULL;
+	CamelSasl *sasl = NULL;
+	CamelIMAP4Command *ic;
+	int id;
+	
+	if (service->url->authmech)
+		mech = g_hash_table_lookup (engine->authtypes, service->url->authmech);
+	
+	if ((!mech || (mech && mech->need_password)) && !service->url->passwd) {
+		guint32 flags = CAMEL_SESSION_PASSWORD_SECRET;
+		char *base_prompt;
+		char *full_prompt;
+		
+		if (reprompt)
+			flags |= CAMEL_SESSION_PASSWORD_REPROMPT;
+		
+		base_prompt = camel_session_build_password_prompt (
+			"IMAP", service->url->user, service->url->host);
+		
+		if (errmsg != NULL)
+			full_prompt = g_strconcat (errmsg, base_prompt, NULL);
+		else
+			full_prompt = g_strdup (base_prompt);
+		
+		service->url->passwd = camel_session_get_password (
+			session, service, NULL, full_prompt,
+			"password", flags, ex);
+		
+		g_free (base_prompt);
+		g_free (full_prompt);
+		
+		if (!service->url->passwd)
+			return FALSE;
+	}
+	
+	if (service->url->authmech) {
+		sasl = camel_sasl_new ("imap", mech->authproto, service);
+		
+		ic = camel_imap4_engine_prequeue (engine, NULL, "AUTHENTICATE %s\r\n", service->url->authmech);
+		ic->plus = sasl_auth;
+		ic->user_data = sasl;
+	} else {
+		ic = camel_imap4_engine_prequeue (engine, NULL, "LOGIN %S %S\r\n",
+						  service->url->user, service->url->passwd);
+	}
+	
+	while ((id = camel_imap4_engine_iterate (engine)) < ic->id && id != -1)
+		;
+	
+	if (sasl != NULL)
+		camel_object_unref (sasl);
+	
+	if (id == -1 || ic->status == CAMEL_IMAP4_COMMAND_ERROR) {
+		/* unrecoverable error */
+		camel_exception_xfer (ex, &ic->ex);
+		camel_imap4_command_unref (ic);
+		
+		return FALSE;
+	}
+	
+	if (ic->result != CAMEL_IMAP4_RESULT_OK) {
+		camel_imap4_command_unref (ic);
+		
+		/* try again */
+		
+		return TRUE;
+	}
+	
+	camel_imap4_command_unref (ic);
+	
+	return FALSE;
+}
+
+static gboolean
+imap4_reconnect (CamelIMAP4Engine *engine, CamelException *ex)
+{
+	CamelService *service = engine->service;
+	gboolean reprompt = FALSE;
+	char *errmsg = NULL;
+	CamelException lex;
+	
+	if (!connect_to_server_wrapper (engine, ex))
+		return FALSE;
+	
+	if (engine->state != CAMEL_IMAP4_ENGINE_AUTHENTICATED) {
+#define CANT_USE_AUTHMECH (!g_hash_table_lookup (engine->authtypes, service->url->authmech))
+		if (service->url->authmech && CANT_USE_AUTHMECH) {
+			/* Oops. We can't AUTH using the requested mechanism */
+			camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE,
+					      _("Cannot authenticate to IMAP server %s using %s"),
+					      service->url->host, service->url->authmech);
+			
+			return FALSE;
+		}
+		
+		camel_exception_init (&lex);
+		while (imap4_try_authenticate (engine, reprompt, errmsg, &lex)) {
+			g_free (errmsg);
+			errmsg = g_markup_escape_text (lex.desc, -1);
+			camel_exception_clear (&lex);
+			g_free (service->url->passwd);
+			service->url->passwd = NULL;
+			reprompt = TRUE;
+		}
+		g_free (errmsg);
+		
+		if (camel_exception_is_set (&lex)) {
+			camel_exception_xfer (ex, &lex);
+			return FALSE;
+		}
+	}
+	
+	if (camel_imap4_engine_namespace (engine, ex) == -1)
+		return FALSE;
+	
+	camel_imap4_store_summary_set_namespaces (((CamelIMAP4Store *) service)->summary, &engine->namespaces);
+	
+	return TRUE;
+}
+
+static gboolean
+imap4_connect (CamelService *service, CamelException *ex)
+{
+	CamelIMAP4Store *store = (CamelIMAP4Store *) service;
+	gboolean retval;
+	
+	if (((CamelOfflineStore *) store)->state == CAMEL_OFFLINE_STORE_NETWORK_UNAVAIL)
+		return TRUE;
+	
+	CAMEL_SERVICE_REC_LOCK (service, connect_lock);
+	if (store->engine->state == CAMEL_IMAP4_ENGINE_DISCONNECTED)
+		retval = imap4_reconnect (store->engine, ex);
+	else
+		retval = TRUE;
+	CAMEL_SERVICE_REC_UNLOCK (service, connect_lock);
+	
+	return retval;
+}
+
+static gboolean
+imap4_disconnect (CamelService *service, gboolean clean, CamelException *ex)
+{
+	CamelIMAP4Store *store = (CamelIMAP4Store *) service;
+	CamelIMAP4Command *ic;
+	int id;
+	
+	if (((CamelOfflineStore *) store)->state == CAMEL_OFFLINE_STORE_NETWORK_UNAVAIL)
+		return TRUE;
+	
+	CAMEL_SERVICE_REC_LOCK (store, connect_lock);
+	if (clean && store->engine->state != CAMEL_IMAP4_ENGINE_DISCONNECTED) {
+		ic = camel_imap4_engine_queue (store->engine, NULL, "LOGOUT\r\n");
+		while ((id = camel_imap4_engine_iterate (store->engine)) < ic->id && id != -1)
+			;
+		
+		camel_imap4_command_unref (ic);
+	}
+	CAMEL_SERVICE_REC_UNLOCK (store, connect_lock);
+	
+	return 0;
+}
+
+extern CamelServiceAuthType camel_imap4_password_authtype;
+
+static GList *
+imap4_query_auth_types (CamelService *service, CamelException *ex)
+{
+	CamelIMAP4Store *store = (CamelIMAP4Store *) service;
+	CamelServiceAuthType *authtype;
+	GList *sasl_types, *t, *next;
+	gboolean connected;
+	
+	if (((CamelOfflineStore *) store)->state == CAMEL_OFFLINE_STORE_NETWORK_UNAVAIL)
+		return NULL;
+	
+	CAMEL_SERVICE_REC_LOCK (store, connect_lock);
+	connected = connect_to_server_wrapper (store->engine, ex);
+	CAMEL_SERVICE_REC_UNLOCK (store, connect_lock);
+	if (!connected)
+		return NULL;
+	
+	sasl_types = camel_sasl_authtype_list (FALSE);
+	for (t = sasl_types; t; t = next) {
+		authtype = t->data;
+		next = t->next;
+		
+		if (!g_hash_table_lookup (store->engine->authtypes, authtype->authproto)) {
+			sasl_types = g_list_remove_link (sasl_types, t);
+			g_list_free_1 (t);
+		}
+	}
+	
+	return g_list_prepend (sasl_types, &camel_imap4_password_authtype);
+}
+
+static char *
+imap4_folder_utf7_name (CamelStore *store, const char *folder_name, char wildcard)
+{
+	char *real_name, *p;
+	char sep = '\0';
+	int len;
+	
+	if (*folder_name) {
+		sep = camel_imap4_get_path_delim (((CamelIMAP4Store *) store)->summary, folder_name);
+		
+		if (sep != '/') {
+			p = real_name = g_alloca (strlen (folder_name) + 1);
+			strcpy (real_name, folder_name);
+			while (*p != '\0') {
+				if (*p == '/')
+					*p = sep;
+				p++;
+			}
+			
+			folder_name = real_name;
+		}
+		
+		real_name = camel_utf8_utf7 (folder_name);
+	} else
+		real_name = g_strdup ("");
+	
+	if (wildcard) {
+		len = strlen (real_name);
+		real_name = g_realloc (real_name, len + 3);
+		
+		if (len > 0)
+			real_name[len++] = sep;
+		
+		real_name[len++] = wildcard;
+		real_name[len] = '\0';
+	}
+	
+	return real_name;
+}
+
+static CamelFolder *
+imap4_get_folder (CamelStore *store, const char *folder_name, guint32 flags, CamelException *ex)
+{
+	CamelIMAP4Engine *engine = ((CamelIMAP4Store *) store)->engine;
+	CamelFolder *folder = NULL;
+	camel_imap4_list_t *list;
+	CamelIMAP4Command *ic;
+	CamelFolderInfo *fi;
+	GPtrArray *array;
+	char *utf7_name;
+	int create;
+	int id, i;
+	
+	CAMEL_SERVICE_REC_LOCK (store, connect_lock);
+	
+	if (((CamelOfflineStore *) store)->state == CAMEL_OFFLINE_STORE_NETWORK_UNAVAIL) {
+		if ((flags & CAMEL_STORE_FOLDER_CREATE) != 0) {
+			camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM, _("Cannot create IMAP folders in offline mode."));
+		} else {
+			folder = camel_imap4_folder_new (store, folder_name, ex);
+		}
+		
+		CAMEL_SERVICE_REC_UNLOCK (store, connect_lock);
+		
+		return folder;
+	}
+	
+	/* make sure the folder exists - try LISTing it? */
+	utf7_name = imap4_folder_utf7_name (store, folder_name, '\0');
+	ic = camel_imap4_engine_queue (engine, NULL, "LIST \"\" %S\r\n", utf7_name);
+	camel_imap4_command_register_untagged (ic, "LIST", camel_imap4_untagged_list);
+	ic->user_data = array = g_ptr_array_new ();
+	g_free (utf7_name);
+	
+	while ((id = camel_imap4_engine_iterate (engine)) < ic->id && id != -1)
+		;
+	
+	if (id == -1 || ic->status != CAMEL_IMAP4_COMMAND_COMPLETE) {
+		camel_exception_xfer (ex, &ic->ex);
+		camel_imap4_command_unref (ic);
+		g_ptr_array_free (array, TRUE);
+		goto done;
+	}
+	
+	create = array->len == 0;
+	
+	for (i = 0; i < array->len; i++) {
+		list = array->pdata[i];
+		g_free (list->name);
+		g_free (list);
+	}
+	
+	g_ptr_array_free (array, TRUE);
+	
+	if (ic->result != CAMEL_IMAP4_RESULT_OK) {
+		camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+				      _("Cannot get folder `%s' on IMAP server %s: Unknown"),
+				      folder_name, ((CamelService *) store)->url->host);
+		camel_imap4_command_unref (ic);
+		goto done;
+	}
+	
+	camel_imap4_command_unref (ic);
+	
+	if (create) {
+		const char *basename;
+		char *parent;
+		int len;
+		
+		if (!(flags & CAMEL_STORE_FOLDER_CREATE))
+			goto done;
+		
+		if (!(basename = strrchr (folder_name, '/')))
+			basename = folder_name;
+		else
+			basename++;
+		
+		len = basename > folder_name ? (basename - folder_name) - 1 : 0;
+		parent = g_alloca (len + 1);
+		memcpy (parent, folder_name, len);
+		parent[len] = '\0';
+		
+		if (!(fi = imap4_create_folder (store, parent, basename, ex)))
+			goto done;
+		
+		camel_store_free_folder_info (store, fi);
+	}
+	
+	folder = camel_imap4_folder_new (store, folder_name, ex);
+	
+ done:
+	
+	CAMEL_SERVICE_REC_UNLOCK (store, connect_lock);
+	
+	return folder;
+}
+
+
+static gboolean
+imap4_folder_can_contain_folders (CamelStore *store, const char *folder_name, CamelException *ex)
+{
+	CamelIMAP4Engine *engine = ((CamelIMAP4Store *) store)->engine;
+	guint32 flags = CAMEL_FOLDER_NOINFERIORS;
+	camel_imap4_list_t *list;
+	CamelIMAP4Command *ic;
+	GPtrArray *array;
+	char *utf7_name;
+	int id, i;
+	
+	CAMEL_SERVICE_REC_LOCK (store, connect_lock);
+	
+	utf7_name = imap4_folder_utf7_name (store, folder_name, '\0');
+	
+	ic = camel_imap4_engine_queue (engine, NULL, "LIST \"\" %S\r\n", utf7_name);
+	camel_imap4_command_register_untagged (ic, "LIST", camel_imap4_untagged_list);
+	ic->user_data = array = g_ptr_array_new ();
+	
+	while ((id = camel_imap4_engine_iterate (engine)) < ic->id && id != -1)
+		;
+	
+	if (id == -1 || ic->status != CAMEL_IMAP4_COMMAND_COMPLETE) {
+		camel_exception_xfer (ex, &ic->ex);
+		camel_imap4_command_unref (ic);
+		
+		for (i = 0; i < array->len; i++) {
+			list = array->pdata[i];
+			g_free (list->name);
+			g_free (list);
+		}
+		
+		goto done;
+	}
+	
+	if (ic->result != CAMEL_IMAP4_RESULT_OK) {
+		camel_imap4_command_unref (ic);
+		
+		camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+				      _("Cannot get LIST information for `%s' on IMAP server %s: %s"),
+				      folder_name, engine->url->host, ic->result == CAMEL_IMAP4_RESULT_BAD ?
+				      _("Bad command") : _("Unknown"));
+		
+		for (i = 0; i < array->len; i++) {
+			list = array->pdata[i];
+			g_free (list->name);
+			g_free (list);
+		}
+		
+		goto done;
+	}
+	
+	flags = 0;
+	for (i = 0; i < array->len; i++) {
+		list = array->pdata[i];
+		if (!strcmp (list->name, utf7_name))
+			flags |= list->flags;
+		g_free (list->name);
+		g_free (list);
+	}
+	
+ done:
+	
+	CAMEL_SERVICE_REC_UNLOCK (store, connect_lock);
+	
+	g_ptr_array_free (array, TRUE);
+	g_free (utf7_name);
+	
+	return (flags & CAMEL_FOLDER_NOINFERIORS) == 0;
+}
+
+static CamelFolderInfo *
+imap4_folder_create (CamelStore *store, const char *folder_name, const char *subfolder_hint, CamelException *ex)
+{
+	CamelIMAP4Engine *engine = ((CamelIMAP4Store *) store)->engine;
+	CamelFolderInfo *fi = NULL;
+	CamelIMAP4Command *ic;
+	char *utf7_name;
+	CamelURL *url;
+	const char *c;
+	int id;
+
+	CAMEL_SERVICE_REC_LOCK (store, connect_lock);
+
+	utf7_name = imap4_folder_utf7_name (store, folder_name, '\0');
+	ic = camel_imap4_engine_queue (engine, NULL, "CREATE %S%s\r\n", utf7_name, subfolder_hint);
+	g_free (utf7_name);
+
+	while ((id = camel_imap4_engine_iterate (engine)) < ic->id && id != -1)
+		;
+
+	if (id == -1 || ic->status != CAMEL_IMAP4_COMMAND_COMPLETE) {
+		camel_exception_xfer (ex, &ic->ex);
+		camel_imap4_command_unref (ic);
+		goto done;
+	}
+
+	switch (ic->result) {
+	case CAMEL_IMAP4_RESULT_OK:
+		url = camel_url_copy (engine->url);
+		camel_url_set_fragment (url, folder_name);
+
+		c = strrchr (folder_name, '/');
+
+		fi = camel_folder_info_new ();
+		fi->full_name = g_strdup (folder_name);
+		fi->name = g_strdup (c ? c + 1: folder_name);
+		fi->uri = camel_url_to_string (url, CAMEL_URL_HIDE_ALL);
+		camel_url_free (url);
+		fi->flags = 0;
+		fi->unread = -1;
+		fi->total = -1;
+		
+		camel_imap4_store_summary_note_info (((CamelIMAP4Store *) store)->summary, fi);
+		
+		camel_object_trigger_event (store, "folder_created", fi);
+		break;
+	case CAMEL_IMAP4_RESULT_NO:
+		/* FIXME: would be good to save the NO reason into the err message */
+		camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+				      _("Cannot create folder `%s': Invalid mailbox name"),
+				      folder_name);
+		break;
+	case CAMEL_IMAP4_RESULT_BAD:
+		camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+				      _("Cannot create folder `%s': Bad command"),
+				      folder_name);
+		break;
+	default:
+		g_assert_not_reached ();
+	}
+	
+	camel_imap4_command_unref (ic);
+	
+ done:
+	
+	CAMEL_SERVICE_REC_UNLOCK (store, connect_lock);
+	
+	return fi;
+}
+
+static gboolean
+imap4_folder_recreate (CamelStore *store, const char *folder_name, CamelException *ex)
+{
+	CamelFolderInfo *fi = NULL;
+	char hint[2];
+	char sep;
+	
+	sep = camel_imap4_get_path_delim (((CamelIMAP4Store *) store)->summary, folder_name);
+	sprintf (hint, "%c", sep);
+	
+	imap4_delete_folder (store, folder_name, ex);
+	if (camel_exception_is_set (ex))
+		return FALSE;
+	
+	if (!(fi = imap4_folder_create (store, folder_name, hint, ex)))
+		return FALSE;
+	
+	camel_folder_info_free (fi);
+	
+	return TRUE;
+}
+
+static CamelFolderInfo *
+imap4_create_folder (CamelStore *store, const char *parent_name, const char *folder_name, CamelException *ex)
+{
+	CamelFolderInfo *fi = NULL;
+	const char *c;
+	char *name;
+	char sep;
+	
+	sep = camel_imap4_get_path_delim (((CamelIMAP4Store *) store)->summary, parent_name);
+	
+	c = folder_name;
+	while (*c != '\0') {
+		if (*c == sep || strchr ("/#%*", *c)) {
+			camel_exception_setv (ex, CAMEL_EXCEPTION_FOLDER_INVALID_PATH,
+					      _("The folder name \"%s\" is invalid because "
+						"it contains the character \"%c\""),
+					      folder_name, *c);
+			return NULL;
+		}
+		
+		c++;
+	}
+	
+	if (((CamelOfflineStore *) store)->state == CAMEL_OFFLINE_STORE_NETWORK_UNAVAIL) {
+		camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM, _("Cannot create IMAP folders in offline mode."));
+		return NULL;
+	}
+	
+	if (parent_name != NULL && *parent_name) {
+		CamelException lex;
+		
+		camel_exception_init (&lex);
+		if (!imap4_folder_can_contain_folders (store, parent_name, &lex)) {
+			if (camel_exception_is_set (&lex)) {
+				camel_exception_xfer (ex, &lex);
+				return NULL;
+			}
+			
+			if (!imap4_folder_recreate (store, parent_name, &lex)) {
+				camel_exception_xfer (ex, &lex);
+				return NULL;
+			}
+		}
+		
+		name = g_strdup_printf ("%s/%s", parent_name, folder_name);
+	} else
+		name = g_strdup (folder_name);
+	
+	fi = imap4_folder_create (store, name, "", ex);
+	g_free (name);
+	
+	return fi;
+}
+
+static void
+imap4_delete_folder (CamelStore *store, const char *folder_name, CamelException *ex)
+{
+	CamelIMAP4Engine *engine = ((CamelIMAP4Store *) store)->engine;
+	CamelFolder *selected = (CamelFolder *) engine->folder;
+	CamelIMAP4Command *ic, *ic0 = NULL;
+	CamelFolderInfo *fi;
+	char *utf7_name;
+	CamelURL *url;
+	const char *p;
+	int id;
+
+	if (!g_ascii_strcasecmp (folder_name, "INBOX")) {
+		camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+				      _("Cannot delete folder `%s': Special folder"),
+				      folder_name);
+
+		return;
+	}
+
+	if (((CamelOfflineStore *) store)->state == CAMEL_OFFLINE_STORE_NETWORK_UNAVAIL) {
+		camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM, _("Cannot delete IMAP folders in offline mode."));
+		return;
+	}
+
+	CAMEL_SERVICE_REC_LOCK (store, connect_lock);
+
+	if (selected && !strcmp (folder_name, selected->full_name))
+		ic0 = camel_imap4_engine_queue (engine, NULL, "CLOSE\r\n");
+
+	utf7_name = imap4_folder_utf7_name (store, folder_name, '\0');
+	ic = camel_imap4_engine_queue (engine, NULL, "DELETE %S\r\n", utf7_name);
+	g_free (utf7_name);
+
+	while ((id = camel_imap4_engine_iterate (engine)) < ic->id && id != -1)
+		;
+
+	if (id == -1 || ic->status != CAMEL_IMAP4_COMMAND_COMPLETE) {
+		if (ic0 && ic0->status != CAMEL_IMAP4_COMMAND_COMPLETE)
+			camel_exception_xfer (ex, &ic0->ex);
+		else
+			camel_exception_xfer (ex, &ic->ex);
+
+		if (ic0 != NULL)
+			camel_imap4_command_unref (ic0);
+
+		camel_imap4_command_unref (ic);
+		CAMEL_SERVICE_REC_UNLOCK (store, connect_lock);
+		return;
+	}
+
+	if (ic0 != NULL)
+		camel_imap4_command_unref (ic0);
+
+	switch (ic->result) {
+	case CAMEL_IMAP4_RESULT_OK:
+		/* deleted */
+		url = camel_url_copy (engine->url);
+		camel_url_set_fragment (url, folder_name);
+
+		p = strrchr (folder_name, '/');
+
+		fi = camel_folder_info_new ();
+		fi->full_name = g_strdup (folder_name);
+		fi->name = g_strdup (p ? p + 1: folder_name);
+		fi->uri = camel_url_to_string (url, CAMEL_URL_HIDE_ALL);
+		camel_url_free (url);
+		fi->flags = 0;
+		fi->unread = -1;
+		fi->total = -1;
+		
+		camel_imap4_store_summary_unnote_info (((CamelIMAP4Store *) store)->summary, fi);
+		
+		camel_object_trigger_event (store, "folder_deleted", fi);
+		
+		camel_folder_info_free (fi);
+		break;
+	case CAMEL_IMAP4_RESULT_NO:
+		/* FIXME: would be good to save the NO reason into the err message */
+		camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+				      _("Cannot delete folder `%s': Invalid mailbox name"),
+				      folder_name);
+		break;
+	case CAMEL_IMAP4_RESULT_BAD:
+		camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+				      _("Cannot delete folder `%s': Bad command"),
+				      folder_name);
+		break;
+	}
+	
+	camel_imap4_command_unref (ic);
+	
+	CAMEL_SERVICE_REC_UNLOCK (store, connect_lock);
+}
+
+static void
+imap4_rename_folder (CamelStore *store, const char *old_name, const char *new_name, CamelException *ex)
+{
+	CamelIMAP4Engine *engine = ((CamelIMAP4Store *) store)->engine;
+	char *old_uname, *new_uname;
+	CamelIMAP4Command *ic;
+	int id;
+	
+	if (!g_ascii_strcasecmp (old_name, "INBOX")) {
+		camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+				      _("Cannot rename folder `%s' to `%s': Special folder"),
+				      old_name, new_name);
+		
+		return;
+	}
+	
+	if (((CamelOfflineStore *) store)->state == CAMEL_OFFLINE_STORE_NETWORK_UNAVAIL) {
+		camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM, _("Cannot rename IMAP folders in offline mode."));
+		return;
+	}
+	
+	CAMEL_SERVICE_REC_LOCK (store, connect_lock);
+	
+	old_uname = imap4_folder_utf7_name (store, old_name, '\0');
+	new_uname = imap4_folder_utf7_name (store, new_name, '\0');
+	
+	ic = camel_imap4_engine_queue (engine, NULL, "RENAME %S %S\r\n", old_uname, new_uname);
+	g_free (old_uname);
+	g_free (new_uname);
+	
+	while ((id = camel_imap4_engine_iterate (engine)) < ic->id && id != -1)
+		;
+	
+	if (id == -1 || ic->status != CAMEL_IMAP4_COMMAND_COMPLETE) {
+		camel_exception_xfer (ex, &ic->ex);
+		camel_imap4_command_unref (ic);
+		CAMEL_SERVICE_REC_UNLOCK (store, connect_lock);
+		return;
+	}
+	
+	switch (ic->result) {
+	case CAMEL_IMAP4_RESULT_OK:
+		/* FIXME: need to update state on the renamed folder object */
+		/* FIXME: need to update cached summary info too */
+		break;
+	case CAMEL_IMAP4_RESULT_NO:
+		/* FIXME: would be good to save the NO reason into the err message */
+		camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+				      _("Cannot rename folder `%s' to `%s': Invalid mailbox name"),
+				      old_name, new_name);
+		break;
+	case CAMEL_IMAP4_RESULT_BAD:
+		camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+				      _("Cannot rename folder `%s' to `%s': Bad command"),
+				      old_name, new_name);
+		break;
+	}
+	
+	camel_imap4_command_unref (ic);
+	
+	CAMEL_SERVICE_REC_UNLOCK (store, connect_lock);
+}
+
+static int
+list_sort (const camel_imap4_list_t **list0, const camel_imap4_list_t **list1)
+{
+	return strcmp ((*list0)->name, (*list1)->name);
+}
+
+static void
+list_remove_duplicates (GPtrArray *array)
+{
+	camel_imap4_list_t *list, *last;
+	int i;
+	
+	last = array->pdata[0];
+	for (i = 1; i < array->len; i++) {
+		list = array->pdata[i];
+		if (!strcmp (list->name, last->name)) {
+			g_ptr_array_remove_index (array, i--);
+			last->flags |= list->flags;
+			g_free (list->name);
+			g_free (list);
+		}
+	}
+}
+
+static char *
+list_parent (camel_imap4_list_t *mbox)
+{
+	const char *d;
+	
+	if (!(d = strrchr (mbox->name, mbox->delim)))
+		return NULL;
+	
+	return g_strndup (mbox->name, d - mbox->name);
+}
+
+/* bloody glib... GPtrArray doesn't have an insert method */
+static void
+array_insert (GPtrArray *array, int index, void *data)
+{
+	int i;
+	
+	if ((index + 1) == array->len) {
+		/* special case, adding to the end of the array */
+		g_ptr_array_add (array, data);
+		return;
+	}
+	
+	if (index >= array->len) {
+		/* special case, adding past the end of the array */
+		g_ptr_array_set_size (array, index + 1);
+		array->pdata[index] = data;
+		return;
+	}
+	
+	g_ptr_array_set_size (array, array->len + 1);
+	
+	/* shift all elements starting at @index 1 position to the right */
+	for (i = array->len - 2; i >= index; i--)
+		array->pdata[i + 1] = array->pdata[i];
+	
+	array->pdata[index] = data;
+}
+
+static void
+list_add_ghosts (GPtrArray *array)
+{
+	camel_imap4_list_t *mbox;
+	GHashTable *list_hash;
+	char delim, *parent;
+	int i = 0;
+	
+	list_hash = g_hash_table_new (g_str_hash, g_str_equal);
+	
+	while (i < array->len) {
+		mbox = array->pdata[i];
+		if ((parent = list_parent (mbox))) {
+			if (!g_hash_table_lookup (list_hash, parent)) {
+				/* ghost folder, insert a fake LIST info w/ a \NoSelect flag */
+				delim = mbox->delim;
+				mbox = g_new (camel_imap4_list_t, 1);
+				mbox->flags = CAMEL_FOLDER_NOSELECT;
+				mbox->name = parent;
+				mbox->delim = delim;
+				
+				g_hash_table_insert (list_hash, parent, mbox);
+				
+				array_insert (array, i, mbox);
+				continue;
+			} else {
+				/* already exists */
+				g_free (parent);
+			}
+		}
+		
+		g_hash_table_insert (list_hash, mbox->name, mbox);
+		
+		i++;
+	}
+	
+	g_hash_table_destroy (list_hash);
+}
+
+static void
+imap4_status (CamelStore *store, CamelFolderInfo *fi)
+{
+	CamelIMAP4Engine *engine = ((CamelIMAP4Store *) store)->engine;
+	camel_imap4_status_attr_t *attr, *next;
+	camel_imap4_status_t *status;
+	CamelIMAP4Command *ic;
+	GPtrArray *array;
+	char *mailbox;
+	int id, i;
+	
+	mailbox = imap4_folder_utf7_name (store, fi->full_name, '\0');
+	ic = camel_imap4_engine_queue (engine, NULL, "STATUS %S (MESSAGES UNSEEN)\r\n", mailbox);
+	g_free (mailbox);
+	
+	camel_imap4_command_register_untagged (ic, "STATUS", camel_imap4_untagged_status);
+	ic->user_data = array = g_ptr_array_new ();
+	
+	while ((id = camel_imap4_engine_iterate (engine)) < ic->id && id != -1)
+		;
+	
+	if (id == -1 || ic->status != CAMEL_IMAP4_COMMAND_COMPLETE) {
+		camel_imap4_command_unref (ic);
+		g_ptr_array_free (array, TRUE);
+		return;
+	}
+	
+	for (i = 0; i < array->len; i++) {
+		status = array->pdata[i];
+		attr = status->attr_list;
+		while (attr != NULL) {
+			next = attr->next;
+			if (attr->type == CAMEL_IMAP4_STATUS_MESSAGES)
+				fi->total = attr->value;
+			else if (attr->type == CAMEL_IMAP4_STATUS_UNSEEN)
+				fi->unread = attr->value;
+			g_free (attr);
+			attr = next;
+		}
+		
+		g_free (status->mailbox);
+		g_free (status);
+	}
+	
+	camel_imap4_command_unref (ic);
+	g_ptr_array_free (array, TRUE);
+}
+
+static void
+imap4_subscription_info (CamelStore *store, CamelFolderInfo *fi)
+{
+	CamelIMAP4Engine *engine = ((CamelIMAP4Store *) store)->engine;
+	camel_imap4_list_t *lsub;
+	CamelIMAP4Command *ic;
+	GPtrArray *array;
+	char *mailbox;
+	int id, i;
+	
+	mailbox = imap4_folder_utf7_name (store, fi->full_name, '\0');
+	ic = camel_imap4_engine_queue (engine, NULL, "LSUB \"\" %S\r\n", mailbox);
+	camel_imap4_command_register_untagged (ic, "LSUB", camel_imap4_untagged_list);
+	ic->user_data = array = g_ptr_array_new ();
+	
+	while ((id = camel_imap4_engine_iterate (engine)) < ic->id && id != -1)
+		;
+	
+	camel_imap4_command_unref (ic);
+	for (i = 0; i < array->len; i++) {
+		lsub = array->pdata[i];
+		
+		if (!strcmp (lsub->name, mailbox))
+			fi->flags |= CAMEL_FOLDER_SUBSCRIBED;
+		
+		g_free (lsub->name);
+		g_free (lsub);
+	}
+	
+	g_ptr_array_free (array, TRUE);
+}
+
+static CamelFolderInfo *
+imap4_build_folder_info (CamelStore *store, const char *top, guint32 flags, GPtrArray *array)
+{
+	CamelIMAP4Engine *engine = ((CamelIMAP4Store *) store)->engine;
+	CamelFolder *folder = (CamelFolder *) engine->folder;
+	gboolean lsub = (flags & CAMEL_STORE_FOLDER_INFO_SUBSCRIBED);
+	camel_imap4_list_t *list;
+	CamelFolderInfo *fi;
+	char *name, *p;
+	CamelURL *url;
+	int i;
+
+	if (array->len == 0) {
+		g_ptr_array_free (array, TRUE);
+		return NULL;
+	}
+
+	g_ptr_array_sort (array, (GCompareFunc) list_sort);
+
+	list_remove_duplicates (array);
+	list_add_ghosts (array);
+	
+	url = camel_url_copy (engine->url);
+
+	if (!strcmp (top, "") && (flags & CAMEL_STORE_FOLDER_INFO_RECURSIVE)) {
+		/* clear the folder-info cache */
+		camel_store_summary_clear ((CamelStoreSummary *) ((CamelIMAP4Store *) store)->summary);
+	}
+
+	for (i = 0; i < array->len; i++) {
+		list = array->pdata[i];
+		fi = camel_folder_info_new ();
+
+		p = name = camel_utf7_utf8 (list->name);
+		while (*p != '\0') {
+			if (*p == list->delim)
+				*p = '/';
+			p++;
+		}
+
+		p = strrchr (name, '/');
+		camel_url_set_fragment (url, name);
+
+		fi->full_name = name;
+		fi->name = g_strdup (p ? p + 1: name);
+		fi->uri = camel_url_to_string (url, CAMEL_URL_HIDE_ALL);
+		fi->flags = list->flags | (lsub ? CAMEL_FOLDER_SUBSCRIBED : 0);
+		fi->unread = -1;
+		fi->total = -1;
+
+		if (!g_ascii_strcasecmp (fi->full_name, "INBOX"))
+			fi->flags |= CAMEL_FOLDER_SYSTEM | CAMEL_FOLDER_TYPE_INBOX;
+
+		/* SELECTED folder, just get it from the folder */
+		if (folder && !strcmp (folder->full_name, fi->full_name)) {
+			camel_object_get (folder, NULL, CAMEL_FOLDER_TOTAL, &fi->total, CAMEL_FOLDER_UNREAD, &fi->unread, 0);
+		} else if (!(flags & CAMEL_STORE_FOLDER_INFO_FAST)) {
+			imap4_status (store, fi);
+		}
+
+		if (!(fi->flags & CAMEL_FOLDER_SUBSCRIBED))
+			imap4_subscription_info (store, fi);
+
+		array->pdata[i] = fi;
+
+		camel_imap4_store_summary_note_info (((CamelIMAP4Store *) store)->summary, fi);
+
+		if (!g_ascii_strcasecmp (fi->full_name, "INBOX")) {
+			g_free (fi->name);
+			fi->name = g_strdup (_("Inbox"));
+		}
+
+		g_free (list->name);
+		g_free (list);
+	}
+
+	fi = camel_folder_info_build (array, top, '/', TRUE);
+
+	camel_url_free (url);
+
+	g_ptr_array_free (array, TRUE);
+
+	camel_store_summary_save ((CamelStoreSummary *) ((CamelIMAP4Store *) store)->summary);
+
+	return fi;
+}
+
+static CamelFolderInfo *
+imap4_get_folder_info (CamelStore *store, const char *top, guint32 flags, CamelException *ex)
+{
+	CamelIMAP4Engine *engine = ((CamelIMAP4Store *) store)->engine;
+	CamelIMAP4Command *ic, *ic0 = NULL, *ic1 = NULL;
+	CamelFolderInfo *inbox = NULL, *fi = NULL;
+	const char *base, *namespace;
+	camel_imap4_list_t *list;
+	GPtrArray *array;
+	const char *cmd;
+	char *pattern;
+	char wildcard;
+	int id, i;
+	
+	if (top == NULL)
+		top = "";
+	
+	if (!(namespace = camel_url_get_param (((CamelService *) store)->url, "namespace")))
+		namespace = "";
+	
+	if (!strcmp (top, ""))
+		base = namespace;
+	else
+		base = top;
+	
+	CAMEL_SERVICE_REC_LOCK (store, connect_lock);
+	
+#ifdef USE_FOLDER_INFO_CACHE_LOGIC_FOR_SPEED
+	if (((CamelOfflineStore *) store)->state == CAMEL_OFFLINE_STORE_NETWORK_UNAVAIL
+	    || engine->state == CAMEL_IMAP4_ENGINE_DISCONNECTED) {
+		fi = camel_imap4_store_summary_get_folder_info (((CamelIMAP4Store *) store)->summary, base, flags);
+		if (base == namespace && *namespace) {
+			inbox = camel_imap4_store_summary_get_folder_info (((CamelIMAP4Store *) store)->summary, "INBOX",
+									   flags & ~CAMEL_STORE_FOLDER_INFO_RECURSIVE);
+			if (inbox) {
+				inbox->next = fi;
+				fi = inbox;
+			}
+		}
+		
+		if (fi == NULL && ((CamelOfflineStore *) store)->state == CAMEL_OFFLINE_STORE_NETWORK_AVAIL) {
+			/* folder info hasn't yet been cached and the store hasn't been
+			 * connected yet, but the network is available so we can connect
+			 * and query the server. */
+			goto check_online;
+		}
+		CAMEL_SERVICE_REC_UNLOCK (store, connect_lock);
+		return fi;
+	}
+#else
+	/* this is the way the old imap code was meant to work (except it was broken and disregarded the
+	 * NETWORK_UNAVAIL state and went online anyway if fi was NULL, but we won't be evil like that)
+	 */
+	if (((CamelOfflineStore *) store)->state == CAMEL_OFFLINE_STORE_NETWORK_UNAVAIL) {
+		fi = camel_imap4_store_summary_get_folder_info (((CamelIMAP4Store *) store)->summary, base, flags);
+		if (base == namespace && *namespace) {
+			inbox = camel_imap4_store_summary_get_folder_info (((CamelIMAP4Store *) store)->summary, "INBOX",
+									   flags & ~CAMEL_STORE_FOLDER_INFO_RECURSIVE);
+			if (inbox) {
+				inbox->next = fi;
+				fi = inbox;
+			}
+		}
+		CAMEL_SERVICE_REC_UNLOCK (store, connect_lock);
+		return fi;
+	}
+#endif
+	
+#ifdef USE_FOLDER_INFO_CACHE_LOGIC_FOR_SPEED
+ check_online:
+#endif
+	
+	if (flags & CAMEL_STORE_FOLDER_INFO_SUBSCRIBED)
+		cmd = "LSUB";
+	else
+		cmd = "LIST";
+	
+	wildcard = (flags & CAMEL_STORE_FOLDER_INFO_RECURSIVE) ? '*' : '%';
+	pattern = imap4_folder_utf7_name (store, base, wildcard);
+	array = g_ptr_array_new ();
+	
+	if (base == namespace && *namespace) {
+		/* Make sure to get INBOX: we always use LIST so the user sees his/her INBOX even
+		   if it isn't subscribed and the user has enabled "Only show subscribed folders" */
+		ic1 = camel_imap4_engine_queue (engine, NULL, "LIST \"\" INBOX\r\n");
+		camel_imap4_command_register_untagged (ic1, "LIST", camel_imap4_untagged_list);
+		ic1->user_data = array;
+	}
+	
+	if (*top != '\0') {
+		size_t len;
+		char sep;
+		
+		len = strlen (pattern);
+		sep = pattern[len - 2];
+		pattern[len - 2] = '\0';
+		
+		ic0 = camel_imap4_engine_queue (engine, NULL, "%s \"\" %S\r\n", cmd, pattern);
+		camel_imap4_command_register_untagged (ic0, cmd, camel_imap4_untagged_list);
+		ic0->user_data = array;
+		
+		pattern[len - 2] = sep;
+	}
+	
+	ic = camel_imap4_engine_queue (engine, NULL, "%s \"\" %S\r\n", cmd, pattern);
+	camel_imap4_command_register_untagged (ic, cmd, camel_imap4_untagged_list);
+	ic->user_data = array;
+	
+	while ((id = camel_imap4_engine_iterate (engine)) < ic->id && id != -1)
+		;
+	
+	if (id == -1 || ic->status != CAMEL_IMAP4_COMMAND_COMPLETE) {
+		if (ic1 && ic1->status != CAMEL_IMAP4_COMMAND_COMPLETE)
+			camel_exception_xfer (ex, &ic1->ex);
+		else if (ic0 && ic0->status != CAMEL_IMAP4_COMMAND_COMPLETE)
+			camel_exception_xfer (ex, &ic0->ex);
+		else
+			camel_exception_xfer (ex, &ic->ex);
+		
+		if (ic1 != NULL)
+			camel_imap4_command_unref (ic1);
+		
+		if (ic0 != NULL)
+			camel_imap4_command_unref (ic0);
+		
+		camel_imap4_command_unref (ic);
+		
+		for (i = 0; i < array->len; i++) {
+			list = array->pdata[i];
+			g_free (list->name);
+			g_free (list);
+		}
+		
+		g_ptr_array_free (array, TRUE);
+		g_free (pattern);
+		
+		goto done;
+	}
+	
+	if (ic1 != NULL)
+		camel_imap4_command_unref (ic1);
+	
+	if (ic0 != NULL)
+		camel_imap4_command_unref (ic0);
+	
+	if (ic->result != CAMEL_IMAP4_RESULT_OK) {
+		camel_imap4_command_unref (ic);
+		
+		camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+				      _("Cannot get %s information for pattern `%s' on IMAP server %s: %s"),
+				      cmd, pattern, engine->url->host, ic->result == CAMEL_IMAP4_RESULT_BAD ?
+				      _("Bad command") : _("Unknown"));
+		
+		for (i = 0; i < array->len; i++) {
+			list = array->pdata[i];
+			g_free (list->name);
+			g_free (list);
+		}
+		
+		g_ptr_array_free (array, TRUE);
+		
+		g_free (pattern);
+		
+		goto done;
+	}
+	
+	g_free (pattern);
+	
+	fi = imap4_build_folder_info (store, top, flags, array);
+	
+ done:
+	
+	CAMEL_SERVICE_REC_UNLOCK (store, connect_lock);
+	
+	return fi;
+}
+
+static gboolean
+imap4_folder_subscribed (CamelStore *store, const char *folder_name)
+{
+	CamelIMAP4Store *imap4_store = (CamelIMAP4Store *) store;
+	CamelStoreInfo *si;
+	int truth = FALSE;
+	
+	if ((si = camel_store_summary_path ((CamelStoreSummary *) imap4_store->summary, folder_name))) {
+		truth = (si->flags & CAMEL_STORE_INFO_FOLDER_SUBSCRIBED) != 0;
+		camel_store_summary_info_free ((CamelStoreSummary *) imap4_store->summary, si);
+	}
+	
+	return truth;
+}
+
+static void
+imap4_subscribe_folder (CamelStore *store, const char *folder_name, CamelException *ex)
+{
+	CamelIMAP4Engine *engine = ((CamelIMAP4Store *) store)->engine;
+	CamelIMAP4Command *ic;
+	CamelFolderInfo *fi;
+	char *utf7_name;
+	CamelURL *url;
+	const char *p;
+	int id;
+
+	if (((CamelOfflineStore *) store)->state == CAMEL_OFFLINE_STORE_NETWORK_UNAVAIL) {
+		camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM, _("Cannot subscribe to IMAP folders in offline mode."));
+		return;
+	}
+
+	CAMEL_SERVICE_REC_LOCK (store, connect_lock);
+
+	utf7_name = imap4_folder_utf7_name (store, folder_name, '\0');
+	ic = camel_imap4_engine_queue (engine, NULL, "SUBSCRIBE %S\r\n", utf7_name);
+	g_free (utf7_name);
+
+	while ((id = camel_imap4_engine_iterate (engine)) < ic->id && id != -1)
+		;
+
+	if (id == -1 || ic->status != CAMEL_IMAP4_COMMAND_COMPLETE) {
+		camel_exception_xfer (ex, &ic->ex);
+		camel_imap4_command_unref (ic);
+		CAMEL_SERVICE_REC_UNLOCK (store, connect_lock);
+		return;
+	}
+
+	switch (ic->result) {
+	case CAMEL_IMAP4_RESULT_OK:
+		/* subscribed */
+		url = camel_url_copy (engine->url);
+		camel_url_set_fragment (url, folder_name);
+
+		p = strrchr (folder_name, '/');
+
+		fi = camel_folder_info_new ();
+		fi->full_name = g_strdup (folder_name);
+		fi->name = g_strdup (p ? p + 1: folder_name);
+		fi->uri = camel_url_to_string (url, CAMEL_URL_HIDE_ALL);
+		camel_url_free (url);
+		fi->flags = CAMEL_FOLDER_NOCHILDREN;
+		fi->unread = -1;
+		fi->total = -1;
+		
+		camel_imap4_store_summary_note_info (((CamelIMAP4Store *) store)->summary, fi);
+		
+		camel_object_trigger_event (store, "folder_subscribed", fi);
+		camel_folder_info_free (fi);
+		break;
+	case CAMEL_IMAP4_RESULT_NO:
+		/* FIXME: would be good to save the NO reason into the err message */
+		camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+				      _("Cannot subscribe to folder `%s': Invalid mailbox name"),
+				      folder_name);
+		break;
+	case CAMEL_IMAP4_RESULT_BAD:
+		camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+				      _("Cannot subscribe to folder `%s': Bad command"),
+				      folder_name);
+		break;
+	}
+	
+	camel_imap4_command_unref (ic);
+	
+	CAMEL_SERVICE_REC_UNLOCK (store, connect_lock);
+}
+
+static void
+imap4_unsubscribe_folder (CamelStore *store, const char *folder_name, CamelException *ex)
+{
+	CamelIMAP4Engine *engine = ((CamelIMAP4Store *) store)->engine;
+	CamelIMAP4Command *ic;
+	CamelFolderInfo *fi;
+	char *utf7_name;
+	CamelURL *url;
+	const char *p;
+	int id;
+
+	if (((CamelOfflineStore *) store)->state == CAMEL_OFFLINE_STORE_NETWORK_UNAVAIL) {
+		camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM, _("Cannot unsubscribe from IMAP folders in offline mode."));
+		return;
+	}
+
+	CAMEL_SERVICE_REC_LOCK (store, connect_lock);
+
+	utf7_name = imap4_folder_utf7_name (store, folder_name, '\0');
+	ic = camel_imap4_engine_queue (engine, NULL, "UNSUBSCRIBE %S\r\n", utf7_name);
+	g_free (utf7_name);
+
+	while ((id = camel_imap4_engine_iterate (engine)) < ic->id && id != -1)
+		;
+
+	if (id == -1 || ic->status != CAMEL_IMAP4_COMMAND_COMPLETE) {
+		camel_exception_xfer (ex, &ic->ex);
+		camel_imap4_command_unref (ic);
+		CAMEL_SERVICE_REC_UNLOCK (store, connect_lock);
+		return;
+	}
+
+	switch (ic->result) {
+	case CAMEL_IMAP4_RESULT_OK:
+		/* unsubscribed */
+		url = camel_url_copy (engine->url);
+		camel_url_set_fragment (url, folder_name);
+
+		p = strrchr (folder_name, '/');
+
+		fi = camel_folder_info_new ();
+		fi->full_name = g_strdup (folder_name);
+		fi->name = g_strdup (p ? p + 1: folder_name);
+		fi->uri = camel_url_to_string (url, CAMEL_URL_HIDE_ALL);
+		camel_url_free (url);
+		fi->flags = 0;
+		fi->unread = -1;
+		fi->total = -1;
+		
+		camel_imap4_store_summary_unnote_info (((CamelIMAP4Store *) store)->summary, fi);
+		
+		camel_object_trigger_event (store, "folder_unsubscribed", fi);
+		camel_folder_info_free (fi);
+		break;
+	case CAMEL_IMAP4_RESULT_NO:
+		/* FIXME: would be good to save the NO reason into the err message */
+		camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+				      _("Cannot unsubscribe from folder `%s': Invalid mailbox name"),
+				      folder_name);
+		break;
+	case CAMEL_IMAP4_RESULT_BAD:
+		camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+				      _("Cannot unsubscribe from folder `%s': Bad command"),
+				      folder_name);
+		break;
+	}
+	
+	camel_imap4_command_unref (ic);
+	
+	CAMEL_SERVICE_REC_UNLOCK (store, connect_lock);
+}
+
+static void
+imap4_noop (CamelStore *store, CamelException *ex)
+{
+	CamelIMAP4Engine *engine = ((CamelIMAP4Store *) store)->engine;
+	CamelFolder *folder = (CamelFolder *) engine->folder;
+	CamelIMAP4Command *ic;
+	int id;
+	
+	if (((CamelOfflineStore *) store)->state == CAMEL_OFFLINE_STORE_NETWORK_UNAVAIL)
+		return;
+	
+	CAMEL_SERVICE_REC_LOCK (store, connect_lock);
+	
+	if (folder) {
+		camel_folder_sync (folder, FALSE, ex);
+		if (camel_exception_is_set (ex)) {
+			CAMEL_SERVICE_REC_UNLOCK (store, connect_lock);
+			return;
+		}
+	}
+	
+	ic = camel_imap4_engine_queue (engine, NULL, "NOOP\r\n");
+	while ((id = camel_imap4_engine_iterate (engine)) < ic->id && id != -1)
+		;
+	
+	if (id == -1 || ic->status != CAMEL_IMAP4_COMMAND_COMPLETE)
+		camel_exception_xfer (ex, &ic->ex);
+	
+	camel_imap4_command_unref (ic);
+	
+	if (folder && !camel_exception_is_set (ex))
+		camel_imap4_summary_flush_updates (folder->summary, ex);
+	
+	CAMEL_SERVICE_REC_UNLOCK (store, connect_lock);
+}

Added: trunk/imap4/camel-imap4-store.h
==============================================================================
--- (empty file)
+++ trunk/imap4/camel-imap4-store.h	Sat May 10 17:53:05 2008
@@ -0,0 +1,60 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*  Camel
+ *  Copyright (C) 1999-2007 Novell, Inc. (www.novell.com)
+ *
+ *  Authors: Jeffrey Stedfast <fejj novell com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU Lesser General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  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 Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+
+#ifndef __CAMEL_IMAP4_STORE_H__
+#define __CAMEL_IMAP4_STORE_H__
+
+#include <camel/camel-offline-store.h>
+
+#define CAMEL_TYPE_IMAP4_STORE            (camel_imap4_store_get_type ())
+#define CAMEL_IMAP4_STORE(obj)            (CAMEL_CHECK_CAST ((obj), CAMEL_TYPE_IMAP4_STORE, CamelIMAP4Store))
+#define CAMEL_IMAP4_STORE_CLASS(klass)    (CAMEL_CHECK_CLASS_CAST ((klass), CAMEL_TYPE_IMAP4_STORE, CamelIMAP4StoreClass))
+#define CAMEL_IS_IMAP4_STORE(obj)         (CAMEL_CHECK_TYPE ((obj), CAMEL_TYPE_IMAP4_STORE))
+#define CAMEL_IS_IMAP4_STORE_CLASS(klass) (CAMEL_CHECK_CLASS_TYPE ((klass), CAMEL_TYPE_IMAP4_STORE))
+#define CAMEL_IMAP4_STORE_GET_CLASS(obj)  (CAMEL_CHECK_GET_CLASS ((obj), CAMEL_TYPE_IMAP4_STORE, CamelIMAP4StoreClass))
+
+G_BEGIN_DECLS
+
+typedef struct _CamelIMAP4Store CamelIMAP4Store;
+typedef struct _CamelIMAP4StoreClass CamelIMAP4StoreClass;
+
+struct _CamelIMAP4Engine;
+
+struct _CamelIMAP4Store {
+	CamelOfflineStore parent_object;
+	
+	struct _CamelIMAP4StoreSummary *summary;
+	struct _CamelIMAP4Engine *engine;
+	char *storage_path;
+};
+
+struct _CamelIMAP4StoreClass {
+	CamelOfflineStoreClass parent_class;
+	
+};
+
+
+CamelType camel_imap4_store_get_type (void);
+
+G_END_DECLS
+
+#endif /* __CAMEL_IMAP4_STORE_H__ */

Added: trunk/imap4/camel-imap4-stream.c
==============================================================================
--- (empty file)
+++ trunk/imap4/camel-imap4-stream.c	Sat May 10 17:53:05 2008
@@ -0,0 +1,734 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*  Camel
+ *  Copyright (C) 1999-2007 Novell, Inc. (www.novell.com)
+ *
+ *  Authors: Jeffrey Stedfast <fejj novell com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU Lesser General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  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 Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <ctype.h>
+
+#include <camel/camel-debug.h>
+
+#include "camel-imap4-specials.h"
+#include "camel-imap4-stream.h"
+
+#define d(x) (camel_debug ("imap4:stream") ? (x) : 0)
+
+#define IMAP4_TOKEN_LEN  128
+
+static void camel_imap4_stream_class_init (CamelIMAP4StreamClass *klass);
+static void camel_imap4_stream_init (CamelIMAP4Stream *stream, CamelIMAP4StreamClass *klass);
+static void camel_imap4_stream_finalize (CamelObject *object);
+
+static ssize_t stream_read (CamelStream *stream, char *buffer, size_t n);
+static ssize_t stream_write (CamelStream *stream, const char *buffer, size_t n);
+static int stream_flush  (CamelStream *stream);
+static int stream_close  (CamelStream *stream);
+static gboolean stream_eos (CamelStream *stream);
+
+
+static CamelStreamClass *parent_class = NULL;
+
+
+CamelType
+camel_imap4_stream_get_type (void)
+{
+	static CamelType type = 0;
+	
+	if (!type) {
+		type = camel_type_register (CAMEL_STREAM_TYPE,
+					    "CamelIMAP4Stream",
+					    sizeof (CamelIMAP4Stream),
+					    sizeof (CamelIMAP4StreamClass),
+					    (CamelObjectClassInitFunc) camel_imap4_stream_class_init,
+					    NULL,
+					    (CamelObjectInitFunc) camel_imap4_stream_init,
+					    (CamelObjectFinalizeFunc) camel_imap4_stream_finalize);
+	}
+	
+	return type;
+}
+
+static void
+camel_imap4_stream_class_init (CamelIMAP4StreamClass *klass)
+{
+	CamelStreamClass *stream_class = (CamelStreamClass *) klass;
+	
+	parent_class = (CamelStreamClass *) camel_type_get_global_classfuncs (CAMEL_STREAM_TYPE);
+	
+	/* virtual method overload */
+	stream_class->read = stream_read;
+	stream_class->write = stream_write;
+	stream_class->flush = stream_flush;
+	stream_class->close = stream_close;
+	stream_class->eos = stream_eos;
+}
+
+static void
+camel_imap4_stream_init (CamelIMAP4Stream *imap4, CamelIMAP4StreamClass *klass)
+{
+	imap4->stream = NULL;
+	
+	imap4->mode = CAMEL_IMAP4_STREAM_MODE_TOKEN;
+	imap4->disconnected = FALSE;
+	imap4->have_unget = FALSE;
+	imap4->eol = FALSE;
+	
+	imap4->literal = 0;
+	
+	imap4->inbuf = imap4->realbuf + IMAP4_READ_PRELEN;
+	imap4->inptr = imap4->inbuf;
+	imap4->inend = imap4->inbuf;
+	
+	imap4->tokenbuf = g_malloc (IMAP4_TOKEN_LEN);
+	imap4->tokenptr = imap4->tokenbuf;
+	imap4->tokenleft = IMAP4_TOKEN_LEN;
+}
+
+static void
+camel_imap4_stream_finalize (CamelObject *object)
+{
+	CamelIMAP4Stream *imap4 = (CamelIMAP4Stream *) object;
+	
+	if (imap4->stream)
+		camel_object_unref (imap4->stream);
+	
+	g_free (imap4->tokenbuf);
+}
+
+
+static ssize_t
+imap4_fill (CamelIMAP4Stream *imap4)
+{
+	unsigned char *inbuf, *inptr, *inend;
+	ssize_t nread;
+	size_t inlen;
+	
+	if (imap4->disconnected) {
+		errno = EINVAL;
+		return -1;
+	}
+	
+	inbuf = imap4->inbuf;
+	inptr = imap4->inptr;
+	inend = imap4->inend;
+	inlen = inend - inptr;
+	
+	g_assert (inptr <= inend);
+	
+	/* attempt to align 'inend' with realbuf + SCAN_HEAD */
+	if (inptr >= inbuf) {
+		inbuf -= inlen < IMAP4_READ_PRELEN ? inlen : IMAP4_READ_PRELEN;
+		memmove (inbuf, inptr, inlen);
+		inptr = inbuf;
+		inbuf += inlen;
+	} else if (inptr > imap4->realbuf) {
+		size_t shift;
+		
+		shift = MIN (inptr - imap4->realbuf, inend - inbuf);
+		memmove (inptr - shift, inptr, inlen);
+		inptr -= shift;
+		inbuf = inptr + inlen;
+	} else {
+		/* we can't shift... */
+		inbuf = inend;
+	}
+	
+	imap4->inptr = inptr;
+	imap4->inend = inbuf;
+	inend = imap4->realbuf + IMAP4_READ_PRELEN + IMAP4_READ_BUFLEN - 1;
+	
+	if ((nread = camel_stream_read (imap4->stream, inbuf, inend - inbuf)) == -1)
+		return -1;
+	else if (nread == 0)
+		imap4->disconnected = TRUE;
+	
+	imap4->inend += nread;
+	
+	return imap4->inend - imap4->inptr;
+}
+
+static ssize_t
+stream_read (CamelStream *stream, char *buffer, size_t n)
+{
+	CamelIMAP4Stream *imap4 = (CamelIMAP4Stream *) stream;
+	ssize_t len, nread = 0;
+	
+	if (imap4->mode == CAMEL_IMAP4_STREAM_MODE_LITERAL) {
+		/* don't let our caller read past the end of the literal */
+		n = MIN (n, imap4->literal);
+	}
+	
+	if (imap4->inptr < imap4->inend) {
+		len = MIN (n, imap4->inend - imap4->inptr);
+		memcpy (buffer, imap4->inptr, len);
+		imap4->inptr += len;
+		nread = len;
+	}
+	
+	if (nread < n) {
+		if ((len = camel_stream_read (imap4->stream, buffer + nread, n - nread)) == 0)
+			imap4->disconnected = TRUE;
+		else if (len == -1)
+			return -1;
+		
+		nread += len;
+	}
+	
+	if (imap4->mode == CAMEL_IMAP4_STREAM_MODE_LITERAL) {
+		imap4->literal -= nread;
+		
+		if (imap4->literal == 0) {
+			imap4->mode = CAMEL_IMAP4_STREAM_MODE_TOKEN;
+			imap4->eol = TRUE;
+		}
+	}
+	
+	return nread;
+}
+
+static ssize_t
+stream_write (CamelStream *stream, const char *buffer, size_t n)
+{
+	CamelIMAP4Stream *imap4 = (CamelIMAP4Stream *) stream;
+	ssize_t nwritten;
+	
+	if (imap4->disconnected) {
+		errno = EINVAL;
+		return -1;
+	}
+	
+	if ((nwritten = camel_stream_write (imap4->stream, buffer, n)) == 0)
+		imap4->disconnected = TRUE;
+	
+	return nwritten;
+}
+
+static int
+stream_flush (CamelStream *stream)
+{
+	CamelIMAP4Stream *imap4 = (CamelIMAP4Stream *) stream;
+	
+	return camel_stream_flush (imap4->stream);
+}
+
+static int
+stream_close (CamelStream *stream)
+{
+	CamelIMAP4Stream *imap4 = (CamelIMAP4Stream *) stream;
+	
+	if (camel_stream_close (imap4->stream) == -1)
+		return -1;
+	
+	camel_object_unref (imap4->stream);
+	imap4->stream = NULL;
+	
+	imap4->disconnected = TRUE;
+	
+	return 0;
+}
+
+static gboolean
+stream_eos (CamelStream *stream)
+{
+	CamelIMAP4Stream *imap4 = (CamelIMAP4Stream *) stream;
+	
+	if (imap4->eol)
+		return TRUE;
+	
+	if (imap4->disconnected && imap4->inptr == imap4->inend)
+		return TRUE;
+	
+	if (camel_stream_eos (imap4->stream))
+		return TRUE;
+	
+	return FALSE;
+}
+
+
+/**
+ * camel_imap4_stream_new:
+ * @stream: tcp stream
+ *
+ * Returns a new imap4 stream
+ **/
+CamelStream *
+camel_imap4_stream_new (CamelStream *stream)
+{
+	CamelIMAP4Stream *imap4;
+	
+	g_return_val_if_fail (CAMEL_IS_STREAM (stream), NULL);
+	
+	imap4 = (CamelIMAP4Stream *) camel_object_new (CAMEL_TYPE_IMAP4_STREAM);
+	camel_object_ref (stream);
+	imap4->stream = stream;
+	
+	return (CamelStream *) imap4;
+}
+
+
+
+#define token_save(imap4, start, len) G_STMT_START {                         \
+	if (imap4->tokenleft <= len) {                                       \
+		unsigned int tlen, toff;                                    \
+		                                                            \
+		tlen = toff = imap4->tokenptr - imap4->tokenbuf;              \
+		tlen = tlen ? tlen : 1;                                     \
+		                                                            \
+		while (tlen < toff + len)                                   \
+			tlen <<= 1;                                         \
+		                                                            \
+		imap4->tokenbuf = g_realloc (imap4->tokenbuf, tlen + 1);      \
+		imap4->tokenptr = imap4->tokenbuf + toff;                     \
+		imap4->tokenleft = tlen - toff;                              \
+	}                                                                   \
+	                                                                    \
+	memcpy (imap4->tokenptr, start, len);                                \
+	imap4->tokenptr += len;                                              \
+	imap4->tokenleft -= len;                                             \
+} G_STMT_END
+
+#define token_clear(imap4) G_STMT_START {                                    \
+	imap4->tokenleft += imap4->tokenptr - imap4->tokenbuf;                 \
+	imap4->tokenptr = imap4->tokenbuf;                                    \
+	imap4->literal = 0;                                                  \
+} G_STMT_END
+
+
+/**
+ * camel_imap4_stream_next_token:
+ * @stream: imap4 stream
+ * @token: imap4 token
+ *
+ * Reads the next token from the imap4 stream and saves it in @token.
+ *
+ * Returns 0 on success or -1 on fail.
+ **/
+int
+camel_imap4_stream_next_token (CamelIMAP4Stream *stream, camel_imap4_token_t *token)
+{
+	register unsigned char *inptr;
+	unsigned char *inend, *start, *p;
+	gboolean escaped = FALSE;
+	size_t literal = 0;
+	guint32 nz_number;
+	int ret;
+	
+	g_return_val_if_fail (CAMEL_IS_IMAP4_STREAM (stream), -1);
+	g_return_val_if_fail (stream->mode != CAMEL_IMAP4_STREAM_MODE_LITERAL, -1);
+	g_return_val_if_fail (token != NULL, -1);
+	
+	if (stream->have_unget) {
+		memcpy (token, &stream->unget, sizeof (camel_imap4_token_t));
+		stream->have_unget = FALSE;
+		return 0;
+	}
+	
+	token_clear (stream);
+	
+	inptr = stream->inptr;
+	inend = stream->inend;
+	*inend = '\0';
+	
+	do {
+		if (inptr == inend) {
+			if ((ret = imap4_fill (stream)) < 0) {
+				token->token = CAMEL_IMAP4_TOKEN_ERROR;
+				return -1;
+			} else if (ret == 0) {
+				token->token = CAMEL_IMAP4_TOKEN_NO_DATA;
+				return 0;
+			}
+			
+			inptr = stream->inptr;
+			inend = stream->inend;
+			*inend = '\0';
+		}
+		
+		while (*inptr == ' ' || *inptr == '\r')
+			inptr++;
+	} while (inptr == inend);
+	
+	do {
+		if (inptr < inend) {
+			if (*inptr == '"') {
+				/* qstring token */
+				escaped = FALSE;
+				start = inptr;
+				
+				/* eat the beginning " */
+				inptr++;
+				
+				p = inptr;
+				while (inptr < inend) {
+					if (*inptr == '"' && !escaped)
+						break;
+					
+					if (*inptr == '\\' && !escaped) {
+						token_save (stream, p, inptr - p);
+						escaped = TRUE;
+						inptr++;
+						p = inptr;
+					} else {
+						inptr++;
+						escaped = FALSE;
+					}
+				}
+				
+				token_save (stream, p, inptr - p);
+				
+				if (inptr == inend) {
+					stream->inptr = start;
+					goto refill;
+				}
+				
+				/* eat the ending " */
+				inptr++;
+				
+				/* nul-terminate the atom token */
+				token_save (stream, "", 1);
+				
+				token->token = CAMEL_IMAP4_TOKEN_QSTRING;
+				token->v.qstring = stream->tokenbuf;
+				
+				d(fprintf (stderr, "token: \"%s\"\n", token->v.qstring));
+				
+				break;
+			} else if (strchr ("+*()[]\n", *inptr)) {
+				/* special character token */
+				token->token = *inptr++;
+				
+				if (camel_debug ("imap4:stream")) {
+					if (token->token != '\n')
+						fprintf (stderr, "token: %c\n", token->token);
+					else
+						fprintf (stderr, "token: \\n\n");
+				}
+				
+				break;
+			} else if (*inptr == '{') {
+				/* literal identifier token */
+				if ((p = strchr (inptr, '}')) && strchr (p, '\n')) {
+					inptr++;
+					
+					while (isdigit ((int) *inptr) && literal < UINT_MAX / 10)
+						literal = (literal * 10) + (*inptr++ - '0');
+					
+					if (*inptr != '}') {
+						if (isdigit ((int) *inptr))
+							g_warning ("illegal literal identifier: literal too large");
+						else if (*inptr != '+')
+							g_warning ("illegal literal identifier: garbage following size");
+						
+						while (*inptr != '}')
+							inptr++;
+					}
+					
+					/* skip over '}' */
+					inptr++;
+					
+					/* skip over any trailing whitespace */
+					while (*inptr == ' ' || *inptr == '\r')
+						inptr++;
+					
+					if (*inptr != '\n') {
+						g_warning ("illegal token following literal identifier: %s", inptr);
+						
+						/* skip ahead to the eoln */
+						if (!(inptr = strchr (inptr, '\n'))) {
+							stream->inptr = start;
+							goto refill;
+						}
+					}
+					
+					/* skip over '\n' */
+					inptr++;
+					
+					token->token = CAMEL_IMAP4_TOKEN_LITERAL;
+					token->v.literal = literal;
+					
+					d(fprintf (stderr, "token: {%u}\n", literal));
+					
+					stream->mode = CAMEL_IMAP4_STREAM_MODE_LITERAL;
+					stream->literal = literal;
+					stream->eol = FALSE;
+					
+					break;
+				} else {
+					stream->inptr = inptr;
+					goto refill;
+				}
+			} else if (*inptr >= '0' && *inptr <= '9') {
+				/* number token */
+				*inend = '\0';
+				nz_number = strtoul ((char *) inptr, (char **) &start, 10);
+				if (start == inend)
+					goto refill;
+				
+				if (*start == ':' || *start == ',') {
+					/* workaround for 'set' tokens (APPENDUID / COPYUID) */
+					goto atom_token;
+				}
+				
+				inptr = start;
+				token->token = CAMEL_IMAP4_TOKEN_NUMBER;
+				token->v.number = nz_number;
+				
+				d(fprintf (stderr, "token: %u\n", nz_number));
+				
+				break;
+			} else if (is_atom (*inptr)) {
+			atom_token:
+				/* simple atom token */
+				start = inptr;
+				
+				while (inptr < inend && is_atom (*inptr))
+					inptr++;
+				
+				if (inptr == inend) {
+					stream->inptr = start;
+					goto refill;
+				}
+				
+				token_save (stream, start, inptr - start);
+				
+				/* nul-terminate the atom token */
+				token_save (stream, "", 1);
+				
+				if (!strcmp (stream->tokenbuf, "NIL")) {
+					/* special atom token */
+					token->token = CAMEL_IMAP4_TOKEN_NIL;
+					d(fprintf (stderr, "token: NIL\n"));
+				} else {
+					token->token = CAMEL_IMAP4_TOKEN_ATOM;
+					token->v.atom = stream->tokenbuf;
+					d(fprintf (stderr, "token: %s\n", token->v.atom));
+				}
+				
+				break;
+			} else if (*inptr == '\\') {
+				/* possible flag token ("\" atom) */
+				start = inptr++;
+				
+				while (inptr < inend && is_atom (*inptr))
+					inptr++;
+				
+				if (inptr == inend) {
+					stream->inptr = start;
+					goto refill;
+				}
+				
+				/* handle the \* case */
+				if ((inptr - start) == 1 && *inptr == '*')
+					inptr++;
+				
+				if ((inptr - start) > 1) {
+					token_save (stream, start, inptr - start);
+					
+					/* nul-terminate the flag token */
+					token_save (stream, "", 1);
+					
+					token->token = CAMEL_IMAP4_TOKEN_FLAG;
+					token->v.atom = stream->tokenbuf;
+					d(fprintf (stderr, "token: %s\n", token->v.atom));
+				} else {
+					token->token = '\\';
+					d(fprintf (stderr, "token: %c\n", token->token));
+				}
+				break;
+			} else if (is_lwsp (*inptr)) {
+				inptr++;
+			} else {
+				/* unknown character token? */
+				token->token = *inptr++;
+				d(fprintf (stderr, "token: %c\n", token->token));
+				break;
+			}
+		} else {
+		refill:
+			token_clear (stream);
+			
+			if (imap4_fill (stream) <= 0) {
+				token->token = CAMEL_IMAP4_TOKEN_ERROR;
+				return -1;
+			}
+			
+			inptr = stream->inptr;
+			inend = stream->inend;
+			*inend = '\0';
+		}
+	} while (inptr < inend);
+	
+	stream->inptr = inptr;
+	
+	return 0;
+}
+
+
+/**
+ * camel_imap4_stream_unget_token:
+ * @stream: imap4 stream
+ * @token: token to 'unget'
+ *
+ * Ungets an imap4 token (as in ungetc()).
+ *
+ * Note: you may *ONLY* unget a single token. Trying to unget another
+ * token will fail.
+ *
+ * Returns 0 on success or -1 on fail.
+ **/
+int
+camel_imap4_stream_unget_token (CamelIMAP4Stream *stream, camel_imap4_token_t *token)
+{
+	if (stream->have_unget)
+		return -1;
+	
+	if (token->token != CAMEL_IMAP4_TOKEN_NO_DATA) {
+		memcpy (&stream->unget, token, sizeof (camel_imap4_token_t));
+		stream->have_unget = TRUE;
+	}
+	
+	return 0;
+}
+
+
+/**
+ * camel_imap4_stream_line:
+ * @stream: imap4 stream
+ * @line: line pointer
+ * @len: line length
+ *
+ * Reads a single line from the imap4 stream and points @line at an
+ * internal buffer containing the line read and sets @len to the
+ * length of the line buffer.
+ *
+ * Returns -1 on error, 0 if the line read is complete, or 1 if the
+ * read is incomplete.
+ **/
+int
+camel_imap4_stream_line (CamelIMAP4Stream *stream, unsigned char **line, size_t *len)
+{
+	register unsigned char *inptr;
+	unsigned char *inend;
+	
+	g_return_val_if_fail (CAMEL_IS_IMAP4_STREAM (stream), -1);
+	g_return_val_if_fail (stream->mode != CAMEL_IMAP4_STREAM_MODE_LITERAL, -1);
+	g_return_val_if_fail (line != NULL, -1);
+	g_return_val_if_fail (len != NULL, -1);
+	
+	inptr = stream->inptr;
+	inend = stream->inend;
+	
+	*inend = '\0';
+	if (inptr == inend || ((inend - inptr) < 2 && *inptr != '\n')) {
+		if (imap4_fill (stream) == -1 && stream->inptr == stream->inend)
+			return -1;
+	}
+	
+	*line = stream->inptr;
+	inptr = stream->inptr;
+	inend = stream->inend;
+	*inend = '\n';
+	
+	while (*inptr != '\n')
+		inptr++;
+	
+	*len = (inptr - stream->inptr);
+	
+	if (inptr > stream->inptr && inptr[-1] == '\r')
+		inptr[-1] = '\0';
+	
+	if (inptr < inend) {
+		/* got the eoln */
+		inptr[0] = '\0';
+		*len += 1;
+		
+		stream->inptr = inptr + 1;
+		
+		return 0;
+	}
+	
+	stream->inptr = inptr;
+	
+	return 1;
+}
+
+
+/**
+ * camel_imap4_stream_literal:
+ * @stream: IMAP stream
+ * @literal: literal pointer
+ * @len: literal length
+ *
+ * Sets @literal to the beginning of the next chunk of the literal
+ * buffer from the IMAP stream and sets @len to the length of the
+ * @literal buffer.
+ *
+ * Returns >0 if more literal data exists, 0 if the end of the literal
+ * has been reached or -1 on fail.
+ **/
+int
+camel_imap4_stream_literal (CamelIMAP4Stream *stream, unsigned char **literal, size_t *len)
+{
+	unsigned char *inptr, *inend;
+	size_t nread;
+	
+	g_return_val_if_fail (CAMEL_IS_IMAP4_STREAM (stream), -1);
+	g_return_val_if_fail (stream->mode == CAMEL_IMAP4_STREAM_MODE_LITERAL, -1);
+	g_return_val_if_fail (literal != NULL, -1);
+	g_return_val_if_fail (len != NULL, -1);
+	
+	if (stream->eol) {
+		*len = 0;
+		return 0;
+	}
+	
+	if ((stream->inend - stream->inptr) < 1) {
+		/* keep our buffer full to the optimal size */
+		if (imap4_fill (stream) == -1 && stream->inptr == stream->inend)
+			return -1;
+	}
+	
+	*literal = inptr = stream->inptr;
+	inend = stream->inend;
+	if ((inend - inptr) > stream->literal)
+		inend = inptr + stream->literal;
+	else
+		inend = stream->inend;
+	
+	*len = nread = inend - inptr;
+	
+	stream->literal -= nread;
+	stream->inptr += nread;
+	
+	if (stream->literal == 0) {
+		stream->mode = CAMEL_IMAP4_STREAM_MODE_TOKEN;
+		stream->eol = TRUE;
+		return 0;
+	}
+	
+	return 1;
+}

Added: trunk/imap4/camel-imap4-stream.h
==============================================================================
--- (empty file)
+++ trunk/imap4/camel-imap4-stream.h	Sat May 10 17:53:05 2008
@@ -0,0 +1,122 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*  Camel
+ *  Copyright (C) 1999-2007 Novell, Inc. (www.novell.com)
+ *
+ *  Authors: Jeffrey Stedfast <fejj novell com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU Lesser General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  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 Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+
+#ifndef __CAMEL_IMAP4_STREAM_H__
+#define __CAMEL_IMAP4_STREAM_H__
+
+#include <camel/camel-stream.h>
+
+#define CAMEL_TYPE_IMAP4_STREAM     (camel_imap4_stream_get_type ())
+#define CAMEL_IMAP4_STREAM(obj)     (CAMEL_CHECK_CAST ((obj), CAMEL_TYPE_IMAP4_STREAM, CamelIMAP4Stream))
+#define CAMEL_IMAP4_STREAM_CLASS(k) (CAMEL_CHECK_CLASS_CAST ((k), CAMEL_TYPE_IMAP4_STREAM, CamelIMAP4StreamClass))
+#define CAMEL_IS_IMAP4_STREAM(o)    (CAMEL_CHECK_TYPE((o), CAMEL_TYPE_IMAP4_STREAM))
+
+#define IMAP4_READ_PRELEN   128
+#define IMAP4_READ_BUFLEN   4096
+
+G_BEGIN_DECLS
+
+typedef struct _CamelIMAP4Stream CamelIMAP4Stream;
+typedef struct _CamelIMAP4StreamClass CamelIMAP4StreamClass;
+
+enum {
+	CAMEL_IMAP4_TOKEN_NO_DATA       = -8,
+	CAMEL_IMAP4_TOKEN_ERROR         = -7,
+	CAMEL_IMAP4_TOKEN_NIL           = -6,
+	CAMEL_IMAP4_TOKEN_ATOM          = -5,
+	CAMEL_IMAP4_TOKEN_FLAG          = -4,
+	CAMEL_IMAP4_TOKEN_NUMBER        = -3,
+	CAMEL_IMAP4_TOKEN_QSTRING       = -2,
+	CAMEL_IMAP4_TOKEN_LITERAL       = -1,
+	/* CAMEL_IMAP4_TOKEN_CHAR would just be the char we got */
+	CAMEL_IMAP4_TOKEN_EOLN          = '\n',
+	CAMEL_IMAP4_TOKEN_LPAREN        = '(',
+	CAMEL_IMAP4_TOKEN_RPAREN        = ')',
+	CAMEL_IMAP4_TOKEN_ASTERISK      = '*',
+	CAMEL_IMAP4_TOKEN_PLUS          = '+',
+	CAMEL_IMAP4_TOKEN_LBRACKET      = '[',
+	CAMEL_IMAP4_TOKEN_RBRACKET      = ']',
+};
+
+typedef struct _camel_imap4_token_t {
+	int token;
+	union {
+		char *atom;
+		char *flag;
+		char *qstring;
+		size_t literal;
+		guint32 number;
+	} v;
+} camel_imap4_token_t;
+
+enum {
+	CAMEL_IMAP4_STREAM_MODE_TOKEN   = 0,
+	CAMEL_IMAP4_STREAM_MODE_LITERAL = 1,
+};
+
+struct _CamelIMAP4Stream {
+	CamelStream parent_object;
+	
+	CamelStream *stream;
+	
+	guint disconnected:1;  /* disconnected state */
+	guint have_unget:1;    /* have an unget token */
+	guint mode:1;          /* TOKEN vs LITERAL */
+	guint eol:1;           /* end-of-literal */
+	
+	size_t literal;
+	
+	/* i/o buffers */
+	unsigned char realbuf[IMAP4_READ_PRELEN + IMAP4_READ_BUFLEN + 1];
+	unsigned char *inbuf;
+	unsigned char *inptr;
+	unsigned char *inend;
+	
+	/* token buffers */
+	unsigned char *tokenbuf;
+	unsigned char *tokenptr;
+	unsigned int tokenleft;
+	
+	camel_imap4_token_t unget;
+};
+
+struct _CamelIMAP4StreamClass {
+	CamelStreamClass parent_class;
+	
+	/* Virtual methods */
+};
+
+
+/* Standard Camel function */
+CamelType camel_imap4_stream_get_type (void);
+
+CamelStream *camel_imap4_stream_new (CamelStream *stream);
+
+int camel_imap4_stream_next_token (CamelIMAP4Stream *stream, camel_imap4_token_t *token);
+int camel_imap4_stream_unget_token (CamelIMAP4Stream *stream, camel_imap4_token_t *token);
+
+int camel_imap4_stream_line (CamelIMAP4Stream *stream, unsigned char **line, size_t *len);
+int camel_imap4_stream_literal (CamelIMAP4Stream *stream, unsigned char **literal, size_t *len);
+
+G_END_DECLS
+
+#endif /* __CAMEL_IMAP4_STREAM_H__ */

Added: trunk/imap4/camel-imap4-summary.c
==============================================================================
--- (empty file)
+++ trunk/imap4/camel-imap4-summary.c	Sat May 10 17:53:05 2008
@@ -0,0 +1,1667 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*  Camel
+ *  Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ *  Authors: Jeffrey Stedfast <fejj novell com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU Lesser General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  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 Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <limits.h>
+#include <utime.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <ctype.h>
+
+
+#include <glib/gi18n-lib.h>
+
+#include <libedataserver/md5-utils.h>
+
+#include <camel/camel-file-utils.h>
+#include <camel/camel-string-utils.h>
+#include <camel/camel-offline-journal.h>
+
+#include "camel-imap4-command.h"
+#include "camel-imap4-engine.h"
+#include "camel-imap4-folder.h"
+#include "camel-imap4-journal.h"
+#include "camel-imap4-store.h"
+#include "camel-imap4-stream.h"
+#include "camel-imap4-summary.h"
+#include "camel-imap4-utils.h"
+
+#define d(x) x
+
+#define CAMEL_IMAP4_SUMMARY_VERSION  3
+
+#define IMAP_SAVE_INCREMENT 1024
+
+static void camel_imap4_summary_class_init (CamelIMAP4SummaryClass *klass);
+static void camel_imap4_summary_init (CamelIMAP4Summary *summary, CamelIMAP4SummaryClass *klass);
+static void camel_imap4_summary_finalize (CamelObject *object);
+
+static int imap4_header_load (CamelFolderSummary *summary, FILE *fin);
+static int imap4_header_save (CamelFolderSummary *summary, FILE *fout);
+static CamelMessageInfo *imap4_message_info_new_from_header (CamelFolderSummary *summary, struct _camel_header_raw *header);
+static CamelMessageInfo *imap4_message_info_load (CamelFolderSummary *summary, FILE *fin);
+static int imap4_message_info_save (CamelFolderSummary *summary, FILE *fout, CamelMessageInfo *info);
+static CamelMessageInfo *imap4_message_info_clone (CamelFolderSummary *summary, const CamelMessageInfo *mi);
+static CamelMessageContentInfo *imap4_content_info_load (CamelFolderSummary *summary, FILE *in);
+static int imap4_content_info_save (CamelFolderSummary *summary, FILE *out, CamelMessageContentInfo *info);
+
+static CamelFolderSummaryClass *parent_class = NULL;
+
+
+CamelType
+camel_imap4_summary_get_type (void)
+{
+	static CamelType type = 0;
+	
+	if (!type) {
+		type = camel_type_register (CAMEL_FOLDER_SUMMARY_TYPE,
+					    "CamelIMAP4Summary",
+					    sizeof (CamelIMAP4Summary),
+					    sizeof (CamelIMAP4SummaryClass),
+					    (CamelObjectClassInitFunc) camel_imap4_summary_class_init,
+					    NULL,
+					    (CamelObjectInitFunc) camel_imap4_summary_init,
+					    (CamelObjectFinalizeFunc) camel_imap4_summary_finalize);
+	}
+	
+	return type;
+}
+
+
+static void
+camel_imap4_summary_class_init (CamelIMAP4SummaryClass *klass)
+{
+	CamelFolderSummaryClass *summary_class = (CamelFolderSummaryClass *) klass;
+	
+	parent_class = (CamelFolderSummaryClass *) camel_type_get_global_classfuncs (camel_folder_summary_get_type ());
+	
+	summary_class->summary_header_load = imap4_header_load;
+	summary_class->summary_header_save = imap4_header_save;
+	summary_class->message_info_new_from_header = imap4_message_info_new_from_header;
+	summary_class->message_info_load = imap4_message_info_load;
+	summary_class->message_info_save = imap4_message_info_save;
+	summary_class->message_info_clone = imap4_message_info_clone;
+	summary_class->content_info_load = imap4_content_info_load;
+	summary_class->content_info_save = imap4_content_info_save;
+}
+
+static void
+camel_imap4_summary_init (CamelIMAP4Summary *summary, CamelIMAP4SummaryClass *klass)
+{
+	CamelFolderSummary *folder_summary = (CamelFolderSummary *) summary;
+	
+	folder_summary->flags = CAMEL_MESSAGE_ANSWERED | CAMEL_MESSAGE_DELETED |
+		CAMEL_MESSAGE_DRAFT | CAMEL_MESSAGE_FLAGGED | CAMEL_MESSAGE_SEEN;
+	
+	folder_summary->message_info_size = sizeof (CamelIMAP4MessageInfo);
+	folder_summary->content_info_size = sizeof (CamelIMAP4MessageContentInfo);
+	
+	((CamelFolderSummary *) summary)->flags |= CAMEL_IMAP4_SUMMARY_HAVE_MLIST;
+	
+	summary->update_flags = TRUE;
+	summary->uidvalidity_changed = FALSE;
+}
+
+static void
+camel_imap4_summary_finalize (CamelObject *object)
+{
+}
+
+
+CamelFolderSummary *
+camel_imap4_summary_new (CamelFolder *folder)
+{
+	CamelFolderSummary *summary;
+	
+	summary = (CamelFolderSummary *) camel_object_new (CAMEL_TYPE_IMAP4_SUMMARY);
+	summary->folder = folder;
+	
+	return summary;
+}
+
+static int
+imap4_header_load (CamelFolderSummary *summary, FILE *fin)
+{
+	CamelIMAP4Summary *imap4_summary = (CamelIMAP4Summary *) summary;
+	
+	if (CAMEL_FOLDER_SUMMARY_CLASS (parent_class)->summary_header_load (summary, fin) == -1)
+		return -1;
+	
+	if (camel_file_util_decode_fixed_int32 (fin, &imap4_summary->version) == -1)
+		return -1;
+	
+	if (imap4_summary->version > CAMEL_IMAP4_SUMMARY_VERSION) {
+		g_warning ("Unknown IMAP4 summary version\n");
+		errno = EINVAL;
+		return -1;
+	}
+	
+	if (imap4_summary->version == 2) {
+		/* check that we have Mailing-List info */
+		int have_mlist;
+		
+		if (camel_file_util_decode_fixed_int32 (fin, &have_mlist) == -1)
+			return -1;
+		
+		if (have_mlist)
+			summary->flags |= CAMEL_IMAP4_SUMMARY_HAVE_MLIST;
+		else
+			summary->flags ^= CAMEL_IMAP4_SUMMARY_HAVE_MLIST;
+	}
+	
+	if (camel_file_util_decode_fixed_int32 (fin, &imap4_summary->uidvalidity) == -1)
+		return -1;
+	
+	return 0;
+}
+
+static int
+imap4_header_save (CamelFolderSummary *summary, FILE *fout)
+{
+	CamelIMAP4Summary *imap4_summary = (CamelIMAP4Summary *) summary;
+	
+	if (CAMEL_FOLDER_SUMMARY_CLASS (parent_class)->summary_header_save (summary, fout) == -1)
+		return -1;
+	
+	if (camel_file_util_encode_fixed_int32 (fout, CAMEL_IMAP4_SUMMARY_VERSION) == -1)
+		return -1;
+	
+	if (camel_file_util_encode_fixed_int32 (fout, imap4_summary->uidvalidity) == -1)
+		return -1;
+	
+	return 0;
+}
+
+static int
+envelope_decode_address (CamelIMAP4Engine *engine, GString *addrs, CamelException *ex)
+{
+	char *addr, *name = NULL, *user = NULL;
+	struct _camel_header_address *cia;
+	unsigned char *literal = NULL;
+	camel_imap4_token_t token;
+	const char *domain = NULL;
+	int part = 0;
+	size_t n;
+	
+	if (camel_imap4_engine_next_token (engine, &token, ex) == -1)
+		return -1;
+	
+	if (token.token == CAMEL_IMAP4_TOKEN_NIL) {
+		return 0;
+	} else if (token.token != '(') {
+		camel_imap4_utils_set_unexpected_token_error (ex, engine, &token);
+		return -1;
+	}
+	
+	if (addrs->len > 0)
+		g_string_append (addrs, ", ");
+	
+	do {
+		if (camel_imap4_engine_next_token (engine, &token, ex) == -1)
+			goto exception;
+		
+		literal = NULL;
+		switch (token.token) {
+		case CAMEL_IMAP4_TOKEN_NIL:
+			break;
+		case CAMEL_IMAP4_TOKEN_ATOM:
+		case CAMEL_IMAP4_TOKEN_QSTRING:
+			switch (part) {
+			case 0:
+				name = camel_header_decode_string (token.v.qstring, NULL);
+				break;
+			case 2:
+				user = g_strdup (token.v.qstring);
+				break;
+			case 3:
+				domain = token.v.qstring;
+				break;
+			}
+			break;
+		case CAMEL_IMAP4_TOKEN_LITERAL:
+			if (camel_imap4_engine_literal (engine, &literal, &n, ex) == -1)
+				goto exception;
+			
+			switch (part) {
+			case 0:
+				name = camel_header_decode_string (literal, NULL);
+				g_free (literal);
+				break;
+			case 2:
+				user = literal;
+				break;
+			case 3:
+				domain = literal;
+				break;
+			}
+			break;
+		default:
+			camel_imap4_utils_set_unexpected_token_error (ex, engine, &token);
+			goto exception;
+		}
+		
+		part++;
+	} while (part < 4);
+	
+	addr = g_strdup_printf ("%s %s", user, domain);
+	g_free (literal);
+	g_free (user);
+	
+	cia = camel_header_address_new_name (name, addr);
+	g_free (name);
+	g_free (addr);
+	
+	addr = camel_header_address_list_format (cia);
+	camel_header_address_unref (cia);
+	
+	g_string_append (addrs, addr);
+	g_free (addr);
+	
+	if (camel_imap4_engine_next_token (engine, &token, ex) == -1)
+		return -1;
+	
+	if (token.token != ')') {
+		camel_imap4_utils_set_unexpected_token_error (ex, engine, &token);
+		return -1;
+	}
+	
+	return 0;
+	
+ exception:
+	
+	g_free (name);
+	g_free (user);
+	
+	return -1;
+}
+
+static int
+envelope_decode_addresses (CamelIMAP4Engine *engine, char **addrlist, CamelException *ex)
+{
+	camel_imap4_token_t token;
+	GString *addrs;
+	
+	if (camel_imap4_engine_next_token (engine, &token, ex) == -1)
+		return -1;
+	
+	if (token.token == CAMEL_IMAP4_TOKEN_NIL) {
+		*addrlist = NULL;
+		return 0;
+	} else if (token.token != '(') {
+		camel_imap4_utils_set_unexpected_token_error (ex, engine, &token);
+		return -1;
+	}
+	
+	addrs = g_string_new ("");
+	
+	do {
+		if (camel_imap4_engine_next_token (engine, &token, ex) == -1) {
+			g_string_free (addrs, TRUE);
+			return -1;
+		}
+		
+		if (token.token == '(') {
+			camel_imap4_stream_unget_token (engine->istream, &token);
+			
+			if (envelope_decode_address (engine, addrs, ex) == -1) {
+				g_string_free (addrs, TRUE);
+				return -1;
+			}
+		} else if (token.token == ')') {
+			break;
+		} else {
+			camel_imap4_utils_set_unexpected_token_error (ex, engine, &token);
+			return -1;
+		}
+	} while (1);
+	
+	*addrlist = addrs->str;
+	g_string_free (addrs, FALSE);
+	
+	return 0;
+}
+
+static int
+envelope_decode_date (CamelIMAP4Engine *engine, time_t *date, CamelException *ex)
+{
+	unsigned char *literal = NULL;
+	camel_imap4_token_t token;
+	const char *nstring;
+	size_t n;
+	
+	if (camel_imap4_engine_next_token (engine, &token, ex) == -1)
+		return -1;
+	
+	switch (token.token) {
+	case CAMEL_IMAP4_TOKEN_NIL:
+		*date = (time_t) -1;
+		return 0;
+	case CAMEL_IMAP4_TOKEN_ATOM:
+		nstring = token.v.atom;
+		break;
+	case CAMEL_IMAP4_TOKEN_QSTRING:
+		nstring = token.v.qstring;
+		break;
+	case CAMEL_IMAP4_TOKEN_LITERAL:
+		if (camel_imap4_engine_literal (engine, &literal, &n, ex) == -1)
+			return -1;
+		
+		nstring = literal;
+		break;
+	default:
+		camel_imap4_utils_set_unexpected_token_error (ex, engine, &token);
+		return -1;
+	}
+	
+	*date = camel_header_decode_date (nstring, NULL);
+	
+	g_free (literal);
+	
+	return 0;
+}
+
+static int
+envelope_decode_nstring (CamelIMAP4Engine *engine, char **nstring, gboolean rfc2047, CamelException *ex)
+{
+	camel_imap4_token_t token;
+	unsigned char *literal;
+	size_t n;
+	
+	if (camel_imap4_engine_next_token (engine, &token, ex) == -1)
+		return -1;
+	
+	switch (token.token) {
+	case CAMEL_IMAP4_TOKEN_NIL:
+		*nstring = NULL;
+		break;
+	case CAMEL_IMAP4_TOKEN_ATOM:
+		if (rfc2047)
+			*nstring = camel_header_decode_string (token.v.atom, NULL);
+		else
+			*nstring = g_strdup (token.v.atom);
+		break;
+	case CAMEL_IMAP4_TOKEN_QSTRING:
+		if (rfc2047)
+			*nstring = camel_header_decode_string (token.v.qstring, NULL);
+		else
+			*nstring = g_strdup (token.v.qstring);
+		break;
+	case CAMEL_IMAP4_TOKEN_LITERAL:
+		if (camel_imap4_engine_literal (engine, &literal, &n, ex) == -1)
+			return -1;
+		
+		if (rfc2047) {
+			*nstring = camel_header_decode_string (literal, NULL);
+			g_free (literal);
+		} else
+			*nstring = literal;
+		
+		break;
+	default:
+		camel_imap4_utils_set_unexpected_token_error (ex, engine, &token);
+		return -1;
+	}
+	
+	return 0;
+}
+
+static CamelSummaryReferences *
+decode_references (const char *refstr, const char *irtstr)
+{
+	struct _camel_header_references *refs, *irt, *r;
+	CamelSummaryReferences *references;
+	MD5Context checksum;
+	guint8 digest[16];
+	guint32 i, n;
+	
+	refs = camel_header_references_decode (refstr);
+	irt = camel_header_references_inreplyto_decode (irtstr);
+	
+	if (!refs && !irt)
+		return NULL;
+	
+	if (irt) {
+		/* The References field is populated from the `References' and/or `In-Reply-To'
+		   headers. If both headers exist, take the first thing in the In-Reply-To header
+		   that looks like a Message-ID, and append it to the References header. */
+		
+		if (refs) {
+			r = irt;
+			while (r->next != NULL)
+				r = r->next;
+			r->next = refs;
+		}
+		
+		refs = irt;
+	}
+	
+	n = camel_header_references_list_size (&refs);
+	references = g_malloc (sizeof (CamelSummaryReferences) + (sizeof (CamelSummaryMessageID) * (n - 1)));
+	references->size = n;
+	
+	for (i = 0, r = refs; r != NULL; i++, r = r->next) {
+		md5_init (&checksum);
+		md5_update (&checksum, (guchar *) r->id, strlen (r->id));
+		md5_final (&checksum, digest);
+		
+		memcpy (references->references[i].id.hash, digest, sizeof (CamelSummaryMessageID));
+	}
+	
+	camel_header_references_list_clear (&refs);
+	
+	return references;
+}
+
+static int
+decode_envelope (CamelIMAP4Engine *engine, CamelMessageInfo *info, camel_imap4_token_t *token, CamelException *ex)
+{
+	CamelIMAP4MessageInfo *iinfo = (CamelIMAP4MessageInfo *) info;
+	char *nstring, *msgid;
+	MD5Context checksum;
+	guint8 digest[16];
+	
+	if (camel_imap4_engine_next_token (engine, token, ex) == -1)
+		return -1;
+	
+	if (token->token != '(') {
+		camel_imap4_utils_set_unexpected_token_error (ex, engine, token);
+		return -1;
+	}
+	
+	if (envelope_decode_date (engine, &iinfo->info.date_sent, ex) == -1)
+		goto exception;
+	
+	/* subject */
+	if (envelope_decode_nstring (engine, &nstring, TRUE, ex) == -1)
+		goto exception;
+	
+	iinfo->info.subject = camel_pstring_strdup (nstring);
+	g_free (nstring);
+	
+	/* from */
+	if (envelope_decode_addresses (engine, &nstring, ex) == -1)
+		goto exception;
+	
+	iinfo->info.from = camel_pstring_strdup (nstring);
+	g_free (nstring);
+	
+	/* sender */
+	if (envelope_decode_addresses (engine, &nstring, ex) == -1)
+		goto exception;
+	
+	g_free (nstring);
+	
+	/* reply-to */
+	if (envelope_decode_addresses (engine, &nstring, ex) == -1)
+		goto exception;
+	
+	g_free (nstring);
+	
+	/* to */
+	if (envelope_decode_addresses (engine, &nstring, ex) == -1)
+		goto exception;
+	
+	iinfo->info.to = camel_pstring_strdup (nstring);
+	g_free (nstring);
+	
+	/* cc */
+	if (envelope_decode_addresses (engine, &nstring, ex) == -1)
+		goto exception;
+	
+	iinfo->info.cc = camel_pstring_strdup (nstring);
+	g_free (nstring);
+	
+	/* bcc */
+	if (envelope_decode_addresses (engine, &nstring, ex) == -1)
+		goto exception;
+	
+	g_free (nstring);
+	
+	/* in-reply-to */
+	if (envelope_decode_nstring (engine, &nstring, FALSE, ex) == -1)
+		goto exception;
+	
+	if (nstring != NULL) {
+		if (!iinfo->info.references)
+			iinfo->info.references = decode_references (NULL, nstring);
+		
+		g_free (nstring);
+	}
+	
+	/* message-id */
+	if (envelope_decode_nstring (engine, &nstring, FALSE, ex) == -1)
+		goto exception;
+	
+	if (nstring != NULL) {
+		if ((msgid = camel_header_msgid_decode (nstring))) {
+			md5_init (&checksum);
+			md5_update (&checksum, (guchar *) msgid, strlen (msgid));
+			md5_final (&checksum, digest);
+			
+			memcpy (iinfo->info.message_id.id.hash, digest, sizeof (CamelSummaryMessageID));
+			g_free (msgid);
+		}
+		g_free (nstring);
+	}
+	
+	if (camel_imap4_engine_next_token (engine, token, ex) == -1)
+		return -1;
+	
+	if (token->token != ')') {
+		camel_imap4_utils_set_unexpected_token_error (ex, engine, token);
+		goto exception;
+	}
+	
+	return 0;
+	
+ exception:
+	
+	return -1;
+}
+
+static char *tm_months[] = {
+	"Jan", "Feb", "Mar", "Apr", "May", "Jun",
+	"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
+};
+
+static gboolean
+decode_time (const char **in, int *hour, int *min, int *sec)
+{
+	register const unsigned char *inptr = (const unsigned char *) *in;
+	int *val, colons = 0;
+	
+	*hour = *min = *sec = 0;
+	
+	val = hour;
+	for ( ; *inptr && !isspace ((int) *inptr); inptr++) {
+		if (*inptr == ':') {
+			colons++;
+			switch (colons) {
+			case 1:
+				val = min;
+				break;
+			case 2:
+				val = sec;
+				break;
+			default:
+				return FALSE;
+			}
+		} else if (!isdigit ((int) *inptr))
+			return FALSE;
+		else
+			*val = (*val * 10) + (*inptr - '0');
+	}
+	
+	*in = inptr;
+	
+	return TRUE;
+}
+
+static time_t
+mktime_utc (struct tm *tm)
+{
+	time_t tt;
+	
+	tm->tm_isdst = -1;
+	tt = mktime (tm);
+	
+#if defined (HAVE_TM_GMTOFF)
+	tt += tm->tm_gmtoff;
+#elif defined (HAVE_TIMEZONE)
+	if (tm->tm_isdst > 0) {
+#if defined (HAVE_ALTZONE)
+		tt -= altzone;
+#else /* !defined (HAVE_ALTZONE) */
+		tt -= (timezone - 3600);
+#endif
+	} else
+		tt -= timezone;
+#endif
+	
+	return tt;
+}
+
+static time_t
+decode_internaldate (const char *in)
+{
+	const char *inptr = in;
+	int hour, min, sec, n;
+	struct tm tm;
+	time_t date;
+	char *buf;
+	
+	memset ((void *) &tm, 0, sizeof (struct tm));
+	
+	tm.tm_mday = strtoul (inptr, &buf, 10);
+	if (buf == inptr || *buf != '-')
+		return (time_t) -1;
+	
+	inptr = buf + 1;
+	if (inptr[3] != '-')
+		return (time_t) -1;
+	
+	for (n = 0; n < 12; n++) {
+		if (!g_ascii_strncasecmp (inptr, tm_months[n], 3))
+			break;
+	}
+	
+	if (n >= 12)
+		return (time_t) -1;
+	
+	tm.tm_mon = n;
+	
+	inptr += 4;
+	
+	n = strtoul (inptr, &buf, 10);
+	if (buf == inptr || *buf != ' ')
+		return (time_t) -1;
+	
+	tm.tm_year = n - 1900;
+	
+	inptr = buf + 1;
+	if (!decode_time (&inptr, &hour, &min, &sec))
+		return (time_t) -1;
+	
+	tm.tm_hour = hour;
+	tm.tm_min = min;
+	tm.tm_sec = sec;
+	
+	n = strtol (inptr, NULL, 10);
+	
+	date = mktime_utc (&tm);
+	
+	/* date is now GMT of the time we want, but not offset by the timezone ... */
+	
+	/* this should convert the time to the GMT equiv time */
+	date -= ((n / 100) * 60 * 60) + (n % 100) * 60;
+	
+	return date;
+}
+
+enum {
+	IMAP4_FETCH_ENVELOPE     = (1 << 0),
+	IMAP4_FETCH_FLAGS        = (1 << 1),
+	IMAP4_FETCH_INTERNALDATE = (1 << 2),
+	IMAP4_FETCH_RFC822SIZE   = (1 << 3),
+	IMAP4_FETCH_UID          = (1 << 4),
+	
+	IMAP4_FETCH_SAVED        = (1 << 7),
+};
+
+#define IMAP4_FETCH_ALL (IMAP4_FETCH_ENVELOPE | IMAP4_FETCH_FLAGS | IMAP4_FETCH_INTERNALDATE | IMAP4_FETCH_RFC822SIZE | IMAP4_FETCH_UID)
+
+struct imap4_envelope_t {
+	CamelMessageInfo *info;
+	guint8 changed;
+};
+
+struct imap4_fetch_all_t {
+	CamelFolderChangeInfo *changes;
+	CamelFolderSummary *summary;
+	GHashTable *uid_hash;
+	GPtrArray *added;
+	guint32 count;
+	guint32 total;
+	guint32 first;
+	guint8 need;
+	guint8 all;
+};
+
+static void
+imap4_fetch_all_free (struct imap4_fetch_all_t *fetch)
+{
+	struct imap4_envelope_t *envelope;
+	int i;
+	
+	for (i = 0; i < fetch->added->len; i++) {
+		if (!(envelope = fetch->added->pdata[i]))
+			continue;
+		
+		camel_message_info_free (envelope->info);
+		g_free (envelope);
+	}
+	
+	g_ptr_array_free (fetch->added, TRUE);
+	g_hash_table_destroy (fetch->uid_hash);
+	camel_folder_change_info_free (fetch->changes);
+	g_free (fetch);
+}
+
+static void
+courier_imap_is_a_piece_of_shit (CamelFolderSummary *summary, guint32 msg)
+{
+	CamelSession *session = ((CamelService *) summary->folder->parent_store)->session;
+	char *warning;
+	
+	warning = g_strdup_printf ("IMAP server did not respond with an untagged FETCH response "
+				   "for message #%u. This is illegal according to rfc3501 (and "
+				   "the older rfc2060). You will need to contact your\n"
+				   "Administrator(s) (or ISP) and have them resolve this issue.\n\n"
+				   "Hint: If your IMAP server is Courier-IMAP, it is likely that this "
+				   "message is simply unreadable by the IMAP server and will need "
+				   "to be given read permissions.", msg);
+	
+	camel_session_alert_user (session, CAMEL_SESSION_ALERT_WARNING, warning, FALSE);
+	g_free (warning);
+}
+
+/**
+ * imap4_fetch_all_add:
+ * @fetch: FETCH ALL state
+ * @complete: %TRUE if the FETCH command is complete or %FALSE otherwise
+ *
+ * Adds all newly acquired envelopes to the summary. Stops at the
+ * first incomplete envelope.
+ **/
+static void
+imap4_fetch_all_add (struct imap4_fetch_all_t *fetch, gboolean complete)
+{
+	struct imap4_envelope_t *envelope;
+	CamelFolderChangeInfo *changes;
+	CamelMessageInfo *info;
+	guint32 i;
+	
+	changes = fetch->changes;
+	
+	for (i = 0; i < fetch->added->len; i++) {
+		if (!(envelope = fetch->added->pdata[i])) {
+			if (complete)
+				courier_imap_is_a_piece_of_shit (fetch->summary, i + fetch->first);
+			break;
+		}
+		
+		if ((envelope->changed & IMAP4_FETCH_ALL) != IMAP4_FETCH_ALL) {
+			if (complete) {
+				d(fprintf (stderr, "Hmmm, IMAP4 server didn't give us everything for message %d\n",
+					   fetch->first + i));
+			}
+			
+			break;
+		}
+		
+		if (!(envelope->changed & IMAP4_FETCH_SAVED)) {
+			if ((info = camel_folder_summary_uid (fetch->summary, camel_message_info_uid (envelope->info)))) {
+				camel_message_info_free (info);
+				continue;
+			}
+			
+			if ((((CamelMessageInfoBase *) envelope->info)->flags & CAMEL_IMAP4_MESSAGE_RECENT))
+				camel_folder_change_info_recent_uid (changes, camel_message_info_uid (envelope->info));
+			
+			camel_folder_change_info_add_uid (changes, camel_message_info_uid (envelope->info));
+			camel_folder_summary_add (fetch->summary, envelope->info);
+			envelope->changed |= IMAP4_FETCH_SAVED;
+		}
+	}
+	
+	if (complete) {
+		for (i = 0; i < fetch->added->len; i++) {
+			if (!(envelope = fetch->added->pdata[i]))
+				continue;
+			
+			camel_message_info_free (envelope->info);
+			g_free (envelope);
+		}
+		
+		g_ptr_array_free (fetch->added, TRUE);
+		g_hash_table_destroy (fetch->uid_hash);
+	}
+	
+	if (camel_folder_change_info_changed (changes))
+		camel_object_trigger_event (fetch->summary->folder, "folder_changed", changes);
+	
+	if (complete) {
+		camel_folder_change_info_free (changes);
+		g_free (fetch);
+	} else {
+		camel_folder_summary_save (fetch->summary);
+		camel_folder_change_info_clear (changes);
+	}
+}
+
+static void
+imap4_fetch_all_update (struct imap4_fetch_all_t *fetch)
+{
+	CamelIMAP4MessageInfo *iinfo, *new_iinfo;
+	struct imap4_envelope_t *envelope;
+	CamelFolderChangeInfo *changes;
+	CamelMessageInfo *info;
+	guint32 flags;
+	int total, i;
+	
+	changes = fetch->changes;
+	
+	total = camel_folder_summary_count (fetch->summary);
+	for (i = 0; i < total; i++) {
+		info = camel_folder_summary_index (fetch->summary, i);
+		if (!(envelope = g_hash_table_lookup (fetch->uid_hash, camel_message_info_uid (info)))) {
+			/* remove it */
+			camel_folder_change_info_remove_uid (changes, camel_message_info_uid (info));
+			camel_folder_summary_remove (fetch->summary, info);
+			total--;
+			i--;
+		} else if (envelope->changed & IMAP4_FETCH_FLAGS) {
+			/* update it with the new flags */
+			new_iinfo = (CamelIMAP4MessageInfo *) envelope->info;
+			iinfo = (CamelIMAP4MessageInfo *) info;
+			
+			flags = iinfo->info.flags;
+			iinfo->info.flags = camel_imap4_merge_flags (iinfo->server_flags, iinfo->info.flags, new_iinfo->server_flags);
+			iinfo->server_flags = new_iinfo->server_flags;
+			if (iinfo->info.flags != flags)
+				camel_folder_change_info_change_uid (changes, camel_message_info_uid (info));
+		}
+		
+		camel_message_info_free (info);
+	}
+	
+	for (i = 0; i < fetch->added->len; i++) {
+		if (!(envelope = fetch->added->pdata[i])) {
+			courier_imap_is_a_piece_of_shit (fetch->summary, i + fetch->first);
+			continue;
+		}
+		
+		camel_message_info_free (envelope->info);
+		g_free (envelope);
+	}
+	
+	g_ptr_array_free (fetch->added, TRUE);
+	g_hash_table_destroy (fetch->uid_hash);
+	
+	if (camel_folder_change_info_changed (changes))
+		camel_object_trigger_event (fetch->summary->folder, "folder_changed", changes);
+	camel_folder_change_info_free (changes);
+	
+	g_free (fetch);
+}
+
+static int
+untagged_fetch_all (CamelIMAP4Engine *engine, CamelIMAP4Command *ic, guint32 index, camel_imap4_token_t *token, CamelException *ex)
+{
+	struct imap4_fetch_all_t *fetch = ic->user_data;
+	CamelFolderSummary *summary = fetch->summary;
+	struct imap4_envelope_t *envelope = NULL;
+	GPtrArray *added = fetch->added;
+	CamelIMAP4MessageInfo *iinfo;
+	CamelMessageInfo *info;
+	guint32 changed = 0;
+	char uid[16];
+	
+	if (index < fetch->first) {
+		/* This can happen if the connection to the
+		 * server was dropped in a previous attempt at
+		 * this FETCH (ALL) command and some other
+		 * client expunged messages in the range
+		 * before fetch->first in the period between
+		 * our previous attempt and now. */
+		size_t movelen = added->len * sizeof (void *);
+		size_t extra = index - fetch->first;
+		void *dest;
+		
+		g_assert (fetch->all);
+		
+		g_ptr_array_set_size (added, added->len + extra);
+		dest = ((char *) added->pdata) + (extra * sizeof (void *));
+		memmove (dest, added->pdata, movelen);
+		fetch->total += extra;
+		fetch->first = index;
+	} else if (index > (added->len + (fetch->first - 1))) {
+		size_t extra = index - (added->len + (fetch->first - 1));
+		g_ptr_array_set_size (added, added->len + extra);
+		fetch->total += extra;
+	}
+	
+	if (!(envelope = added->pdata[index - fetch->first])) {
+		info = camel_message_info_new (summary);
+		iinfo = (CamelIMAP4MessageInfo *) info;
+		envelope = g_new (struct imap4_envelope_t, 1);
+		added->pdata[index - fetch->first] = envelope;
+		envelope->info = info;
+		envelope->changed = 0;
+	} else {
+		info = envelope->info;
+		iinfo = (CamelIMAP4MessageInfo *) info;
+	}
+	
+	if (camel_imap4_engine_next_token (engine, token, ex) == -1)
+		return -1;
+	
+	/* parse the FETCH response list */
+	if (token->token != '(') {
+		camel_imap4_utils_set_unexpected_token_error (ex, engine, token);
+		return -1;
+	}
+	
+	do {
+		if (camel_imap4_engine_next_token (engine, token, ex) == -1)
+			goto exception;
+		
+		if (token->token == ')' || token->token == '\n')
+			break;
+		
+		if (token->token != CAMEL_IMAP4_TOKEN_ATOM)
+			goto unexpected;
+		
+		if (!strcmp (token->v.atom, "ENVELOPE")) {
+			if (envelope) {
+				if (decode_envelope (engine, info, token, ex) == -1)
+					goto exception;
+				
+				changed |= IMAP4_FETCH_ENVELOPE;
+			} else {
+				CamelMessageInfo *tmp;
+				int rv;
+				
+				g_warning ("Hmmm, server is sending us ENVELOPE data for a message we didn't ask for (message %u)\n",
+					   index);
+				tmp = camel_message_info_new (summary);
+				rv = decode_envelope (engine, tmp, token, ex);
+				camel_message_info_free(tmp);
+				
+				if (rv == -1)
+					goto exception;
+			}
+		} else if (!strcmp (token->v.atom, "FLAGS")) {
+			guint32 server_flags = 0;
+			
+			if (camel_imap4_parse_flags_list (engine, &server_flags, ex) == -1)
+				return -1;
+			
+			iinfo->info.flags = camel_imap4_merge_flags (iinfo->server_flags, iinfo->info.flags, server_flags);
+			iinfo->server_flags = server_flags;
+			
+			changed |= IMAP4_FETCH_FLAGS;
+		} else if (!strcmp (token->v.atom, "INTERNALDATE")) {
+			if (camel_imap4_engine_next_token (engine, token, ex) == -1)
+				goto exception;
+			
+			switch (token->token) {
+			case CAMEL_IMAP4_TOKEN_NIL:
+				iinfo->info.date_received = (time_t) -1;
+				break;
+			case CAMEL_IMAP4_TOKEN_ATOM:
+			case CAMEL_IMAP4_TOKEN_QSTRING:
+				iinfo->info.date_received = decode_internaldate (token->v.qstring);
+				break;
+			default:
+				goto unexpected;
+			}
+			
+			changed |= IMAP4_FETCH_INTERNALDATE;
+		} else if (!strcmp (token->v.atom, "RFC822.SIZE")) {
+			if (camel_imap4_engine_next_token (engine, token, ex) == -1)
+				goto exception;
+			
+			if (token->token != CAMEL_IMAP4_TOKEN_NUMBER)
+				goto unexpected;
+			
+			iinfo->info.size = token->v.number;
+			
+			changed |= IMAP4_FETCH_RFC822SIZE;
+		} else if (!strcmp (token->v.atom, "UID")) {
+			const char *iuid;
+			
+			if (camel_imap4_engine_next_token (engine, token, ex) == -1)
+				goto exception;
+			
+			if (token->token != CAMEL_IMAP4_TOKEN_NUMBER || token->v.number == 0)
+				goto unexpected;
+			
+			sprintf (uid, "%u", token->v.number);
+			iuid = camel_message_info_uid (info);
+			if (iuid != NULL && iuid[0] != '\0') {
+				if (strcmp (iuid, uid) != 0) {
+					d(fprintf (stderr, "Hmmm, UID mismatch for message %u\n", index));
+					g_assert_not_reached ();
+				}
+			} else {
+				g_free (info->uid);
+				info->uid = g_strdup (uid);
+				g_hash_table_insert (fetch->uid_hash, (void *) camel_message_info_uid (info), envelope);
+				changed |= IMAP4_FETCH_UID;
+			}
+		} else if (!strcmp (token->v.atom, "BODY[HEADER.FIELDS")) {
+			/* References, Content-Type, and Mailing-List headers... */
+			CamelContentType *content_type;
+			struct _camel_header_raw *h;
+			CamelMimeParser *parser;
+			unsigned char *literal;
+			const char *refs, *str;
+			char *mlist;
+			size_t n;
+			
+			/* '(' */
+			if (camel_imap4_engine_next_token (engine, token, ex) == -1)
+				goto exception;
+			
+			if (token->token != '(')
+				goto unexpected;
+			
+			/* header name list */
+			do {
+				if (camel_imap4_engine_next_token (engine, token, ex) == -1)
+					goto exception;
+				
+				if (token->token == ')')
+					break;
+				
+				switch (token->token) {
+				case CAMEL_IMAP4_TOKEN_ATOM:
+				case CAMEL_IMAP4_TOKEN_QSTRING:
+					break;
+				case CAMEL_IMAP4_TOKEN_LITERAL:
+					if (camel_imap4_engine_literal (engine, &literal, &n, ex) == -1)
+						return -1;
+					
+					g_free (literal);
+					break;
+				default:
+					goto unexpected;
+				}
+				
+				/* we don't care what the list was... */
+			} while (1);
+			
+			/* ']' */
+			if (camel_imap4_engine_next_token (engine, token, ex) == -1)
+				goto exception;
+			
+			if (token->token != ']')
+				goto unexpected;
+			
+			/* literal */
+			if (camel_imap4_engine_next_token (engine, token, ex) == -1)
+				goto exception;
+			
+			if (token->token != CAMEL_IMAP4_TOKEN_LITERAL)
+				goto unexpected;
+			
+			parser = camel_mime_parser_new ();
+			camel_mime_parser_init_with_stream (parser, (CamelStream *) engine->istream);
+			
+			switch (camel_mime_parser_step (parser, NULL, NULL)) {
+			case CAMEL_MIME_PARSER_STATE_HEADER:
+			case CAMEL_MIME_PARSER_STATE_MESSAGE:
+			case CAMEL_MIME_PARSER_STATE_MULTIPART:
+				h = camel_mime_parser_headers_raw (parser);
+				
+				/* find our mailing-list header */
+				mlist = camel_header_raw_check_mailing_list (&h);
+				iinfo->info.mlist = camel_pstring_strdup (mlist);
+				g_free (mlist);
+				
+				/* check if we possibly have attachments */
+				if ((str = camel_header_raw_find (&h, "Content-Type", NULL))) {
+					content_type = camel_content_type_decode (str);
+					if (camel_content_type_is (content_type, "multipart", "*")
+					    && !camel_content_type_is (content_type, "multipart", "alternative"))
+						iinfo->info.flags |= CAMEL_MESSAGE_ATTACHMENTS;
+					camel_content_type_unref (content_type);
+				}
+				
+				/* check for References: */
+				g_free (iinfo->info.references);
+				refs = camel_header_raw_find (&h, "References", NULL);
+				str = camel_header_raw_find (&h, "In-Reply-To", NULL);
+				iinfo->info.references = decode_references (refs, str);
+			default:
+				break;
+			}
+			
+			camel_object_unref (parser);
+		} else {
+			/* wtf? */
+			d(fprintf (stderr, "huh? %s?...\n", token->v.atom));
+		}
+	} while (1);
+	
+	if (envelope) {
+		envelope->changed |= changed;
+		
+		if ((envelope->changed & fetch->need) == fetch->need) {
+			fetch->count++;
+			
+			/* if we're doing a FETCH ALL and fetch->count
+			 * is a multiple of the IMAP_SAVE_INCREMENT,
+			 * sync the newly fetched envelopes to the
+			 * summary and to disk as a convenience to
+			 * users on flaky networks which might drop
+			 * our connection to the IMAP server at any
+			 * time, thus forcing us to reconnect and lose
+			 * our summary fetching state. */
+			if (fetch->all && (fetch->count % IMAP_SAVE_INCREMENT) == 0)
+				imap4_fetch_all_add (fetch, FALSE);
+			
+			camel_operation_progress (NULL, (fetch->count * 100.0f) / fetch->total);
+		}
+	} else if (changed & IMAP4_FETCH_FLAGS) {
+		camel_folder_change_info_change_uid (fetch->changes, camel_message_info_uid (info));
+	}
+	
+	if (token->token != ')')
+		goto unexpected;
+	
+	return 0;
+	
+ unexpected:
+	
+	camel_imap4_utils_set_unexpected_token_error (ex, engine, token);
+	
+ exception:
+	
+	return -1;
+}
+
+#define IMAP4_ALL "FLAGS INTERNALDATE RFC822.SIZE ENVELOPE"
+#define MAILING_LIST_HEADERS "List-Post List-Id Mailing-List Originator X-Mailing-List X-Loop X-List Sender Delivered-To Return-Path X-BeenThere List-Unsubscribe"
+
+#define BASE_HEADER_FIELDS "Content-Type References In-Reply-To"
+#define MORE_HEADER_FIELDS BASE_HEADER_FIELDS " " MAILING_LIST_HEADERS
+
+static void
+imap4_fetch_all_reset (CamelIMAP4Command *ic, struct imap4_fetch_all_t *fetch)
+{
+	CamelIMAP4Summary *imap4_summary = (CamelIMAP4Summary *) fetch->summary;
+	CamelFolder *folder = fetch->summary->folder;
+	struct imap4_envelope_t *envelope;
+	CamelMessageInfo *info;
+	guint32 seqid, iuid;
+	const char *query;
+	char uid[32];
+	int scount;
+	int i;
+	
+	/* sync everything we've gotten so far to the summary */
+	imap4_fetch_all_add (fetch, FALSE);
+	
+	for (i = 0; i < fetch->added->len; i++) {
+		if (!(envelope = fetch->added->pdata[i]))
+			continue;
+		
+		camel_message_info_free (envelope->info);
+		fetch->added->pdata[i] = NULL;
+		g_free (envelope);
+	}
+	
+	scount = camel_folder_summary_count (fetch->summary);
+	seqid = scount + 1;
+	
+	if (seqid > fetch->first) {
+		/* if we get here, then it means that we managed to
+		 * collect some summary info before the connection
+		 * with the imap server dropped. Update our FETCH
+		 * command state to begin fetching where we left off
+		 * rather than at the beginning. */
+		info = camel_folder_summary_index (fetch->summary, scount - 1);
+		iuid = strtoul (camel_message_info_uid (info), NULL, 10);
+		d(fprintf (stderr, "last known summary id = %d, uid = %s, iuid = %u\n", scount, info->uid, iuid));
+		camel_message_info_free (info);
+		sprintf (uid, "%u", iuid + 1);
+		
+		fetch->total = imap4_summary->exists - scount;
+		g_ptr_array_set_size (fetch->added, fetch->total);
+		fetch->first = seqid;
+		
+		/* now we hack the SpruceIMAPCommand structure... */
+		if (((CamelIMAP4Folder *) folder)->enable_mlist)
+			query = "UID FETCH %s:* (" IMAP4_ALL " BODY.PEEK[HEADER.FIELDS (" MORE_HEADER_FIELDS ")])\r\n";
+		else
+			query = "UID FETCH %s:* (" IMAP4_ALL " BODY.PEEK[HEADER.FIELDS (" BASE_HEADER_FIELDS ")])\r\n";
+		
+		g_free (ic->part->buffer);
+		ic->part->buffer = g_strdup_printf (query, uid);
+		ic->part->buflen = strlen (ic->part->buffer);
+		
+		d(fprintf (stderr, "*** RESETTING FETCH-ALL STATE. New command => %s", ic->part->buffer));
+	} else {
+		/* we didn't manage to fetch any new info before the
+		 * connection dropped... */
+	}
+	
+	camel_folder_change_info_clear (fetch->changes);
+	g_hash_table_remove_all (fetch->uid_hash);
+}
+
+static CamelIMAP4Command *
+imap4_summary_fetch_all (CamelFolderSummary *summary, guint32 seqid, const char *uid)
+{
+	CamelIMAP4Summary *imap4_summary = (CamelIMAP4Summary *) summary;
+	CamelFolder *folder = summary->folder;
+	struct imap4_fetch_all_t *fetch;
+	CamelIMAP4Engine *engine;
+	CamelIMAP4Command *ic;
+	const char *query;
+	guint32 total;
+	
+	engine = ((CamelIMAP4Store *) folder->parent_store)->engine;
+	
+	total = (imap4_summary->exists - seqid) + 1;
+	fetch = g_new (struct imap4_fetch_all_t, 1);
+	fetch->uid_hash = g_hash_table_new (g_str_hash, g_str_equal);
+	fetch->changes = camel_folder_change_info_new ();
+	fetch->added = g_ptr_array_sized_new (total);
+	fetch->summary = summary;
+	fetch->first = seqid;
+	fetch->need = IMAP4_FETCH_ALL;
+	fetch->total = total;
+	fetch->count = 0;
+	fetch->all = TRUE;
+	
+	if (((CamelIMAP4Folder *) folder)->enable_mlist)
+		query = "UID FETCH %s:* (" IMAP4_ALL " BODY.PEEK[HEADER.FIELDS (" MORE_HEADER_FIELDS ")])\r\n";
+	else
+		query = "UID FETCH %s:* (" IMAP4_ALL " BODY.PEEK[HEADER.FIELDS (" BASE_HEADER_FIELDS ")])\r\n";
+	
+	ic = camel_imap4_engine_queue (engine, folder, query, uid);
+	
+	camel_imap4_command_register_untagged (ic, "FETCH", untagged_fetch_all);
+	ic->reset = (CamelIMAP4CommandReset) imap4_fetch_all_reset;
+	ic->user_data = fetch;
+	
+	return ic;
+}
+
+static CamelIMAP4Command *
+imap4_summary_fetch_flags (CamelFolderSummary *summary)
+{
+	CamelIMAP4Summary *imap4_summary = (CamelIMAP4Summary *) summary;
+	CamelFolder *folder = summary->folder;
+	struct imap4_fetch_all_t *fetch;
+	CamelMessageInfo *info[2];
+	CamelIMAP4Engine *engine;
+	CamelIMAP4Command *ic;
+	guint32 total;
+	int scount;
+	
+	engine = ((CamelIMAP4Store *) folder->parent_store)->engine;
+	
+	scount = camel_folder_summary_count (summary);
+	
+	info[0] = camel_folder_summary_index (summary, 0);
+	if (scount > 1)
+		info[1] = camel_folder_summary_index (summary, scount - 1);
+	else
+		info[1] = NULL;
+	
+	total = imap4_summary->exists < scount ? imap4_summary->exists : scount;
+	fetch = g_new (struct imap4_fetch_all_t, 1);
+	fetch->uid_hash = g_hash_table_new (g_str_hash, g_str_equal);
+	fetch->changes = camel_folder_change_info_new ();
+	fetch->added = g_ptr_array_sized_new (total);
+	fetch->summary = summary;
+	fetch->first = 1;
+	fetch->need = IMAP4_FETCH_UID | IMAP4_FETCH_FLAGS;
+	fetch->total = total;
+	fetch->count = 0;
+	fetch->all = FALSE;
+	
+	if (info[1] != NULL) {
+		ic = camel_imap4_engine_queue (engine, folder, "UID FETCH %s:%s (FLAGS)\r\n",
+					       camel_message_info_uid (info[0]),
+					       camel_message_info_uid (info[1]));
+		camel_message_info_free (info[1]);
+	} else {
+		ic = camel_imap4_engine_queue (engine, folder, "UID FETCH %s:* (FLAGS)\r\n",
+					       camel_message_info_uid (info[0]));
+	}
+	
+	camel_message_info_free (info[0]);
+	
+	camel_imap4_command_register_untagged (ic, "FETCH", untagged_fetch_all);
+	ic->user_data = fetch;
+	
+	return ic;
+}
+
+static CamelMessageInfo *
+imap4_message_info_new_from_header (CamelFolderSummary *summary, struct _camel_header_raw *header)
+{
+	CamelMessageInfo *info;
+	
+	info = CAMEL_FOLDER_SUMMARY_CLASS (parent_class)->message_info_new_from_header (summary, header);
+	((CamelIMAP4MessageInfo *) info)->server_flags = 0;
+	
+	return info;
+}
+
+static CamelMessageInfo *
+imap4_message_info_load (CamelFolderSummary *summary, FILE *fin)
+{
+	CamelIMAP4MessageInfo *minfo;
+	CamelMessageInfo *info;
+	
+	if (!(info = CAMEL_FOLDER_SUMMARY_CLASS (parent_class)->message_info_load (summary, fin)))
+		return NULL;
+	
+	minfo = (CamelIMAP4MessageInfo *) info;
+	
+	if (camel_file_util_decode_uint32 (fin, &minfo->server_flags) == -1)
+		goto exception;
+	
+	return info;
+
+ exception:
+	
+	camel_message_info_free(info);
+	
+	return NULL;
+}
+
+static int
+imap4_message_info_save (CamelFolderSummary *summary, FILE *fout, CamelMessageInfo *info)
+{
+	CamelIMAP4MessageInfo *minfo = (CamelIMAP4MessageInfo *) info;
+	
+	if (CAMEL_FOLDER_SUMMARY_CLASS (parent_class)->message_info_save (summary, fout, info) == -1)
+		return -1;
+	
+	if (camel_file_util_encode_uint32 (fout, minfo->server_flags) == -1)
+		return -1;
+	
+	return 0;
+}
+
+static CamelMessageInfo *
+imap4_message_info_clone (CamelFolderSummary *summary, const CamelMessageInfo *mi)
+{
+	const CamelIMAP4MessageInfo *src = (const CamelIMAP4MessageInfo *) mi;
+	CamelIMAP4MessageInfo *dest;
+	
+	dest = (CamelIMAP4MessageInfo *) CAMEL_FOLDER_SUMMARY_CLASS (parent_class)->message_info_clone (summary, mi);
+	dest->server_flags = src->server_flags;
+	
+	/* FIXME: parent clone should do this */
+	dest->info.content = camel_folder_summary_content_info_new (summary);
+	
+	return (CamelMessageInfo *) dest;
+}
+
+static CamelMessageContentInfo *
+imap4_content_info_load (CamelFolderSummary *summary, FILE *in)
+{
+	if (fgetc (in))
+		return CAMEL_FOLDER_SUMMARY_CLASS (parent_class)->content_info_load (summary, in);
+	else
+		return camel_folder_summary_content_info_new (summary);
+}
+
+static int
+imap4_content_info_save (CamelFolderSummary *summary, FILE *out, CamelMessageContentInfo *info)
+{
+	if (info->type) {
+		fputc (1, out);
+		return CAMEL_FOLDER_SUMMARY_CLASS (parent_class)->content_info_save (summary, out, info);
+	} else
+		return fputc (0, out);
+}
+
+
+void
+camel_imap4_summary_set_exists (CamelFolderSummary *summary, guint32 exists)
+{
+	CamelIMAP4Summary *imap4_summary = (CamelIMAP4Summary *) summary;
+	
+	g_return_if_fail (CAMEL_IS_IMAP4_SUMMARY (summary));
+	
+	imap4_summary->exists = exists;
+}
+
+void
+camel_imap4_summary_set_recent (CamelFolderSummary *summary, guint32 recent)
+{
+	CamelIMAP4Summary *imap4_summary = (CamelIMAP4Summary *) summary;
+	
+	g_return_if_fail (CAMEL_IS_IMAP4_SUMMARY (summary));
+	
+	imap4_summary->recent = recent;
+}
+
+void
+camel_imap4_summary_set_unseen (CamelFolderSummary *summary, guint32 unseen)
+{
+	CamelIMAP4Summary *imap4_summary = (CamelIMAP4Summary *) summary;
+	
+	g_return_if_fail (CAMEL_IS_IMAP4_SUMMARY (summary));
+	
+	imap4_summary->unseen = unseen;
+}
+
+void
+camel_imap4_summary_set_uidnext (CamelFolderSummary *summary, guint32 uidnext)
+{
+	g_return_if_fail (CAMEL_IS_IMAP4_SUMMARY (summary));
+	
+	summary->nextuid = uidnext;
+}
+
+static void
+imap4_summary_clear (CamelFolderSummary *summary, gboolean uncache)
+{
+	CamelFolderChangeInfo *changes;
+	CamelMessageInfo *info;
+	int i, count;
+	
+	changes = camel_folder_change_info_new ();
+	count = camel_folder_summary_count (summary);
+	for (i = 0; i < count; i++) {
+		if (!(info = camel_folder_summary_index (summary, i)))
+			continue;
+		
+		camel_folder_change_info_remove_uid (changes, camel_message_info_uid (info));
+		camel_message_info_free(info);
+	}
+	
+	camel_folder_summary_clear (summary);
+	
+	if (uncache)
+		camel_data_cache_clear (((CamelIMAP4Folder *) summary->folder)->cache, "cache", NULL);
+	
+	if (camel_folder_change_info_changed (changes))
+		camel_object_trigger_event (summary->folder, "folder_changed", changes);
+	camel_folder_change_info_free (changes);
+}
+
+void
+camel_imap4_summary_set_uidvalidity (CamelFolderSummary *summary, guint32 uidvalidity)
+{
+	CamelIMAP4Summary *imap4_summary = (CamelIMAP4Summary *) summary;
+	
+	g_return_if_fail (CAMEL_IS_IMAP4_SUMMARY (summary));
+	
+	if (imap4_summary->uidvalidity == uidvalidity)
+		return;
+	
+	imap4_summary_clear (summary, TRUE);
+	
+	imap4_summary->uidvalidity = uidvalidity;
+	
+	imap4_summary->uidvalidity_changed = TRUE;
+}
+
+void
+camel_imap4_summary_expunge (CamelFolderSummary *summary, int seqid)
+{
+	CamelIMAP4Summary *imap4_summary = (CamelIMAP4Summary *) summary;
+	CamelFolderChangeInfo *changes;
+	CamelMessageInfo *info;
+	const char *uid;
+	
+	g_return_if_fail (CAMEL_IS_IMAP4_SUMMARY (summary));
+	
+	seqid--;
+	if (!(info = camel_folder_summary_index (summary, seqid)))
+		return;
+	
+	imap4_summary->exists--;
+	
+	uid = camel_message_info_uid (info);
+	camel_data_cache_remove (((CamelIMAP4Folder *) summary->folder)->cache, "cache", uid, NULL);
+	
+	changes = camel_folder_change_info_new ();
+	camel_folder_change_info_remove_uid (changes, uid);
+	
+	camel_message_info_free(info);
+	camel_folder_summary_remove_index (summary, seqid);
+	
+	camel_object_trigger_event (summary->folder, "folder_changed", changes);
+	camel_folder_change_info_free (changes);
+}
+
+#if 0
+static int
+info_uid_sort (const CamelMessageInfo **info0, const CamelMessageInfo **info1)
+{
+	guint32 uid0, uid1;
+	
+	uid0 = strtoul (camel_message_info_uid (*info0), NULL, 10);
+	uid1 = strtoul (camel_message_info_uid (*info1), NULL, 10);
+	
+	if (uid0 == uid1)
+		return 0;
+	
+	return uid0 < uid1 ? -1 : 1;
+}
+#endif
+
+int
+camel_imap4_summary_flush_updates (CamelFolderSummary *summary, CamelException *ex)
+{
+	CamelIMAP4Folder *imap4_folder = (CamelIMAP4Folder *) summary->folder;
+	CamelIMAP4Summary *imap4_summary = (CamelIMAP4Summary *) summary;
+	CamelOfflineJournal *journal = imap4_folder->journal;
+	CamelIMAP4Engine *engine;
+	CamelMessageInfo *info;
+	CamelIMAP4Command *ic;
+	guint32 iuid, seqid = 0;
+	int scount, id;
+	char uid[16];
+	
+	g_return_val_if_fail (CAMEL_IS_IMAP4_SUMMARY (summary), -1);
+	
+	/* FIXME: what do we do if replaying the journal fails? */
+	camel_offline_journal_replay (journal, NULL);
+	
+	if (imap4_folder->enable_mlist && !(summary->flags & CAMEL_IMAP4_SUMMARY_HAVE_MLIST)) {
+		/* need to refetch all summary info to get info->mlist */
+		imap4_summary_clear (summary, FALSE);
+	}
+	
+	summary->flags = (summary->flags & ~CAMEL_IMAP4_SUMMARY_HAVE_MLIST);
+	if (imap4_folder->enable_mlist)
+		summary->flags |= CAMEL_IMAP4_SUMMARY_HAVE_MLIST;
+	else
+		summary->flags ^= CAMEL_IMAP4_SUMMARY_HAVE_MLIST;
+	
+	engine = ((CamelIMAP4Store *) summary->folder->parent_store)->engine;
+	scount = camel_folder_summary_count (summary);
+	
+	if (imap4_summary->uidvalidity_changed) {
+		/* need to refetch everything */
+		g_assert (scount == 0);
+		seqid = 1;
+	} else if (imap4_summary->update_flags || imap4_summary->exists < scount) {
+		/* this both updates flags and removes messages which
+		 * have since been expunged from the server by another
+		 * client */
+		ic = imap4_summary_fetch_flags (summary);
+		
+		camel_operation_start (NULL, _("Scanning for changed messages"));
+		while ((id = camel_imap4_engine_iterate (engine)) < ic->id && id != -1)
+			;
+		
+		if (id == -1 || ic->status != CAMEL_IMAP4_COMMAND_COMPLETE) {
+			camel_imap4_journal_readd_failed ((CamelIMAP4Journal *) journal);
+			imap4_fetch_all_free (ic->user_data);
+			camel_exception_xfer (ex, &ic->ex);
+			camel_imap4_command_unref (ic);
+			camel_operation_end (NULL);
+			return -1;
+		}
+		
+		imap4_fetch_all_update (ic->user_data);
+		camel_imap4_command_unref (ic);
+		camel_operation_end (NULL);
+		
+		scount = camel_folder_summary_count (summary);
+		if (imap4_summary->exists < scount) {
+			/* broken server? wtf? this should never happen... */
+			camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+					      _("IMAP server %s is in an inconsistent state."),
+					      engine->url->host);
+			return -1;
+		} else if (imap4_summary->exists > scount) {
+			/* need to fetch new envelopes */
+			seqid = scount + 1;
+		} else {
+			/* we are fully synced */
+			seqid = 0;
+		}
+	} else {
+		/* need to fetch new envelopes */
+		seqid = scount + 1;
+	}
+	
+	if (seqid != 0 && seqid <= imap4_summary->exists) {
+		if (scount > 0) {
+			info = camel_folder_summary_index (summary, scount - 1);
+			iuid = strtoul (camel_message_info_uid (info), NULL, 10);
+			camel_message_info_free (info);
+			sprintf (uid, "%u", iuid + 1);
+		} else {
+			strcpy (uid, "1");
+		}
+		
+		ic = imap4_summary_fetch_all (summary, seqid, uid);
+		
+		camel_operation_start (NULL, _("Fetching envelopes of new messages"));
+		while ((id = camel_imap4_engine_iterate (engine)) < ic->id && id != -1)
+			;
+		
+		if (id == -1 || ic->status != CAMEL_IMAP4_COMMAND_COMPLETE) {
+			camel_imap4_journal_readd_failed ((CamelIMAP4Journal *) journal);
+			imap4_fetch_all_free (ic->user_data);
+			camel_exception_xfer (ex, &ic->ex);
+			camel_imap4_command_unref (ic);
+			camel_operation_end (NULL);
+			return -1;
+		}
+		
+		imap4_fetch_all_add (ic->user_data, TRUE);
+		camel_imap4_command_unref (ic);
+		camel_operation_end (NULL);
+	}
+	
+	imap4_summary->update_flags = FALSE;
+	imap4_summary->uidvalidity_changed = FALSE;
+	
+	camel_imap4_journal_readd_failed ((CamelIMAP4Journal *) journal);
+	
+	return 0;
+}

Added: trunk/imap4/camel-imap4-summary.h
==============================================================================
--- (empty file)
+++ trunk/imap4/camel-imap4-summary.h	Sat May 10 17:53:05 2008
@@ -0,0 +1,101 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*  Camel
+ *  Copyright (C) 1999-2007 Novell, Inc. (www.novell.com)
+ *
+ *  Authors: Jeffrey Stedfast <fejj novell com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU Lesser General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  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 Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+
+#ifndef __CAMEL_IMAP4_SUMMARY_H__
+#define __CAMEL_IMAP4_SUMMARY_H__
+
+#include <sys/types.h>
+
+#include <camel/camel-folder.h>
+#include <camel/camel-folder-summary.h>
+
+#define CAMEL_TYPE_IMAP4_SUMMARY            (camel_imap4_summary_get_type ())
+#define CAMEL_IMAP4_SUMMARY(obj)            (CAMEL_CHECK_CAST ((obj), CAMEL_TYPE_IMAP4_SUMMARY, CamelIMAP4Summary))
+#define CAMEL_IMAP4_SUMMARY_CLASS(klass)    (CAMEL_CHECK_CLASS_CAST ((klass), CAMEL_TYPE_IMAP4_SUMMARY, CamelIMAP4SummaryClass))
+#define CAMEL_IS_IMAP4_SUMMARY(obj)         (CAMEL_CHECK_TYPE ((obj), CAMEL_TYPE_IMAP4_SUMMARY))
+#define CAMEL_IS_IMAP4_SUMMARY_CLASS(klass) (CAMEL_CHECK_CLASS_TYPE ((klass), CAMEL_TYPE_IMAP4_SUMMARY))
+#define CAMEL_IMAP4_SUMMARY_GET_CLASS(obj)  (CAMEL_CHECK_GET_CLASS ((obj), CAMEL_TYPE_FOLDER_SUMMARY, CamelIMAP4SummaryClass))
+
+G_BEGIN_DECLS
+
+typedef struct _CamelIMAP4MessageInfo CamelIMAP4MessageInfo;
+typedef struct _CamelIMAP4MessageContentInfo CamelIMAP4MessageContentInfo;
+
+typedef struct _CamelIMAP4Summary CamelIMAP4Summary;
+typedef struct _CamelIMAP4SummaryClass CamelIMAP4SummaryClass;
+
+#define CAMEL_IMAP4_MESSAGE_RECENT (1 << 17)
+
+enum {
+	CAMEL_IMAP4_SUMMARY_HAVE_MLIST = (1 << 8)
+};
+
+struct _CamelIMAP4MessageInfo {
+	CamelMessageInfoBase info;
+	
+	guint32 server_flags;
+};
+
+struct _CamelIMAP4MessageContentInfo {
+	CamelMessageContentInfo info;
+	
+};
+
+struct _CamelIMAP4Summary {
+	CamelFolderSummary parent_object;
+	
+	guint32 version;
+	
+	guint32 exists;
+	guint32 recent;
+	guint32 unseen;
+	
+	guint32 uidvalidity;
+	
+	guint uidvalidity_changed:1;
+	guint update_flags:1;
+};
+
+struct _CamelIMAP4SummaryClass {
+	CamelFolderSummaryClass parent_class;
+	
+};
+
+
+CamelType camel_imap4_summary_get_type (void);
+
+CamelFolderSummary *camel_imap4_summary_new (CamelFolder *folder);
+
+void camel_imap4_summary_set_exists (CamelFolderSummary *summary, guint32 exists);
+void camel_imap4_summary_set_recent (CamelFolderSummary *summary, guint32 recent);
+void camel_imap4_summary_set_unseen (CamelFolderSummary *summary, guint32 unseen);
+void camel_imap4_summary_set_uidnext (CamelFolderSummary *summary, guint32 uidnext);
+
+void camel_imap4_summary_set_uidvalidity (CamelFolderSummary *summary, guint32 uidvalidity);
+
+void camel_imap4_summary_expunge (CamelFolderSummary *summary, int seqid);
+
+int camel_imap4_summary_flush_updates (CamelFolderSummary *summary, CamelException *ex);
+
+G_END_DECLS
+
+#endif /* __CAMEL_IMAP4_SUMMARY_H__ */

Added: trunk/imap4/camel-imap4-utils.c
==============================================================================
--- (empty file)
+++ trunk/imap4/camel-imap4-utils.c	Sat May 10 17:53:05 2008
@@ -0,0 +1,734 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*  Camel
+ *  Copyright (C) 1999-2007 Novell, Inc. (www.novell.com)
+ *
+ *  Authors: Jeffrey Stedfast <fejj novell com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU Lesser General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  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 Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <ctype.h>
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <glib/gi18n-lib.h>
+
+#include <camel/camel-net-utils.h>
+#include <camel/camel-store.h>
+
+#include "camel-imap4-command.h"
+#include "camel-imap4-engine.h"
+#include "camel-imap4-store-summary.h"
+#include "camel-imap4-stream.h"
+#include "camel-imap4-summary.h"
+#include "camel-imap4-utils.h"
+
+#define d(x)
+
+
+void
+camel_imap4_flags_diff (flags_diff_t *diff, guint32 old, guint32 new)
+{
+	diff->changed = old ^ new;
+	diff->bits = new & diff->changed;
+}
+
+
+guint32
+camel_imap4_flags_merge (flags_diff_t *diff, guint32 flags)
+{
+	return (flags & ~diff->changed) | diff->bits;
+}
+
+
+/**
+ * camel_imap4_merge_flags:
+ * @original: original server flags
+ * @local: local flags (after changes)
+ * @server: new server flags (another client updated the server flags)
+ *
+ * Merge the local flag changes into the new server flags.
+ *
+ * Returns the merged flags.
+ **/
+guint32
+camel_imap4_merge_flags (guint32 original, guint32 local, guint32 server)
+{
+	flags_diff_t diff;
+	
+	camel_imap4_flags_diff (&diff, original, local);
+	
+	return camel_imap4_flags_merge (&diff, server);
+}
+
+
+void
+camel_imap4_namespace_clear (CamelIMAP4Namespace **ns)
+{
+	CamelIMAP4Namespace *node, *next;
+	
+	node = *ns;
+	while (node != NULL) {
+		next = node->next;
+		g_free (node->path);
+		g_free (node);
+		node = next;
+	}
+	
+	*ns = NULL;
+}
+
+static CamelIMAP4Namespace *
+imap4_namespace_copy (const CamelIMAP4Namespace *ns)
+{
+	CamelIMAP4Namespace *list, *node, *tail;
+	
+	list = NULL;
+	tail = (CamelIMAP4Namespace *) &list;
+	
+	while (ns != NULL) {
+		tail->next = node = g_malloc (sizeof (CamelIMAP4Namespace));
+		node->path = g_strdup (ns->path);
+		node->sep = ns->sep;
+		ns = ns->next;
+		tail = node;
+	}
+	
+	tail->next = NULL;
+	
+	return list;
+}
+
+CamelIMAP4NamespaceList *
+camel_imap4_namespace_list_copy (const CamelIMAP4NamespaceList *nsl)
+{
+	CamelIMAP4NamespaceList *new;
+	
+	new = g_malloc (sizeof (CamelIMAP4NamespaceList));
+	new->personal = imap4_namespace_copy (nsl->personal);
+	new->other = imap4_namespace_copy (nsl->other);
+	new->shared = imap4_namespace_copy (nsl->shared);
+	
+	return new;
+}
+
+void
+camel_imap4_namespace_list_free (CamelIMAP4NamespaceList *nsl)
+{
+	camel_imap4_namespace_clear (&nsl->personal);
+	camel_imap4_namespace_clear (&nsl->shared);
+	camel_imap4_namespace_clear (&nsl->other);
+	g_free (nsl);
+}
+
+
+char
+camel_imap4_get_path_delim (CamelIMAP4StoreSummary *s, const char *full_name)
+{
+	CamelIMAP4Namespace *namespace;
+	const char *slash;
+	size_t len;
+	char *top;
+	
+	g_return_val_if_fail (s->namespaces != NULL, '/');
+	
+	if ((slash = strchr (full_name, '/')))
+		len = (slash - full_name);
+	else
+		len = strlen (full_name);
+	
+	top = g_alloca (len + 1);
+	memcpy (top, full_name, len);
+	top[len] = '\0';
+	
+	if (!g_ascii_strcasecmp (top, "INBOX"))
+		strcpy (top, "INBOX");
+	
+ retry:
+	namespace = s->namespaces->personal;
+	while (namespace != NULL) {
+		if (!strcmp (namespace->path, top))
+			return namespace->sep;
+		namespace = namespace->next;
+	}
+	
+	namespace = s->namespaces->other;
+	while (namespace != NULL) {
+		if (!strcmp (namespace->path, top))
+			return namespace->sep;
+		namespace = namespace->next;
+	}
+	
+	namespace = s->namespaces->shared;
+	while (namespace != NULL) {
+		if (!strcmp (namespace->path, top))
+			return namespace->sep;
+		namespace = namespace->next;
+	}
+	
+	if (top[0] != '\0') {
+		/* look for a default namespace? */
+		top[0] = '\0';
+		goto retry;
+	}
+	
+	return '/';
+}
+
+
+struct _uidset_range {
+	struct _uidset_range *next;
+	guint32 first, last;
+	uint8_t buflen;
+	char buf[24];
+};
+
+struct _uidset {
+	CamelFolderSummary *summary;
+	struct _uidset_range *ranges;
+	struct _uidset_range *tail;
+	size_t maxlen, setlen;
+};
+
+static void
+uidset_range_free (struct _uidset_range *range)
+{
+	struct _uidset_range *next;
+	
+	while (range != NULL) {
+		next = range->next;
+		g_free (range);
+		range = next;
+	}
+}
+
+static void
+uidset_init (struct _uidset *uidset, CamelFolderSummary *summary, size_t maxlen)
+{
+	uidset->ranges = g_new (struct _uidset_range, 1);
+	uidset->ranges->first = (guint32) -1;
+	uidset->ranges->last = (guint32) -1;
+	uidset->ranges->next = NULL;
+	uidset->ranges->buflen = 0;
+	
+	uidset->tail = uidset->ranges;
+	uidset->summary = summary;
+	uidset->maxlen = maxlen;
+	uidset->setlen = 0;
+}
+
+/* returns: -1 on full-and-not-added, 0 on added-and-not-full or 1 on added-and-full */
+static int
+uidset_add (struct _uidset *uidset, CamelMessageInfo *info)
+{
+	GPtrArray *messages = uidset->summary->messages;
+	struct _uidset_range *node, *tail = uidset->tail;
+	const char *iuid = camel_message_info_uid (info);
+	size_t uidlen, len;
+	const char *colon;
+	guint32 index;
+	
+	/* Note: depends on integer overflow for initial 'add' */
+	for (index = tail->last + 1; index < messages->len; index++) {
+		if (info == messages->pdata[index])
+			break;
+	}
+	
+	g_assert (index < messages->len);
+	
+	uidlen = strlen (iuid);
+	
+	if (tail->buflen == 0) {
+		/* first add */
+		tail->first = tail->last = index;
+		strcpy (tail->buf, iuid);
+		uidset->setlen = uidlen;
+		tail->buflen = uidlen;
+	} else if (index == (tail->last + 1)) {
+		/* add to last range */
+		if (tail->last == tail->first) {
+			/* make sure we've got enough room to add this one... */
+			if ((uidset->setlen + uidlen + 1) > uidset->maxlen)
+				return -1;
+			
+			tail->buf[tail->buflen++] = ':';
+			uidset->setlen++;
+		} else {
+			colon = strchr (tail->buf, ':') + 1;
+			
+			len = strlen (colon);
+			uidset->setlen -= len;
+			tail->buflen -= len;
+		}
+		
+		strcpy (tail->buf + tail->buflen, iuid);
+		uidset->setlen += uidlen;
+		tail->buflen += uidlen;
+		
+		tail->last = index;
+	} else if ((uidset->setlen + uidlen + 1) < uidset->maxlen) {
+		/* the beginning of a new range */
+		tail->next = node = g_new (struct _uidset_range, 1);
+		node->first = node->last = index;
+		strcpy (node->buf, iuid);
+		uidset->setlen += uidlen + 1;
+		node->buflen = uidlen;
+		uidset->tail = node;
+		node->next = NULL;
+	} else {
+		/* can't add this one... */
+		return -1;
+	}
+	
+	d(fprintf (stderr, "added uid %s to uidset (summary index = %u)\n", iuid, index));
+	
+	if (uidset->setlen < uidset->maxlen)
+		return 0;
+	
+	return 1;
+}
+
+static char *
+uidset_to_string (struct _uidset *uidset)
+{
+	struct _uidset_range *range;
+	GString *string;
+	char *str;
+	
+	string = g_string_new ("");
+	
+	range = uidset->ranges;
+	while (range != NULL) {
+		g_string_append (string, range->buf);
+		range = range->next;
+		if (range)
+			g_string_append_c (string, ',');
+	}
+	
+	str = string->str;
+	g_string_free (string, FALSE);
+	
+	return str;
+}
+
+int
+camel_imap4_get_uid_set (CamelIMAP4Engine *engine, CamelFolderSummary *summary, GPtrArray *infos, int cur, size_t linelen, char **set)
+{
+	struct _uidset uidset;
+	size_t maxlen;
+	int rv = 0;
+	int i;
+	
+	if (engine->maxlentype == CAMEL_IMAP4_ENGINE_MAXLEN_LINE)
+		maxlen = engine->maxlen - linelen;
+	else
+		maxlen = engine->maxlen;
+	
+	uidset_init (&uidset, summary, maxlen);
+	
+	for (i = cur; i < infos->len && rv != 1; i++) {
+		if ((rv = uidset_add (&uidset, infos->pdata[i])) == -1)
+			break;
+	}
+	
+	if (i > cur)
+		*set = uidset_to_string (&uidset);
+	
+	uidset_range_free (uidset.ranges);
+	
+	return (i - cur);
+}
+
+
+void
+camel_imap4_utils_set_unexpected_token_error (CamelException *ex, CamelIMAP4Engine *engine, camel_imap4_token_t *token)
+{
+	GString *errmsg;
+	
+	if (ex == NULL)
+		return;
+	
+	errmsg = g_string_new ("");
+	g_string_append_printf (errmsg, _("Unexpected token in response from IMAP server %s: "),
+				engine->url->host);
+	
+	switch (token->token) {
+	case CAMEL_IMAP4_TOKEN_NIL:
+		g_string_append (errmsg, "NIL");
+		break;
+	case CAMEL_IMAP4_TOKEN_ATOM:
+		g_string_append (errmsg, token->v.atom);
+		break;
+	case CAMEL_IMAP4_TOKEN_FLAG:
+		g_string_append (errmsg, token->v.flag);
+		break;
+	case CAMEL_IMAP4_TOKEN_QSTRING:
+		g_string_append (errmsg, token->v.qstring);
+		break;
+	case CAMEL_IMAP4_TOKEN_LITERAL:
+		g_string_append_printf (errmsg, "{%u}", token->v.literal);
+		break;
+	case CAMEL_IMAP4_TOKEN_NUMBER:
+		g_string_append_printf (errmsg, "%u", token->v.number);
+		break;
+	case CAMEL_IMAP4_TOKEN_NO_DATA:
+		g_string_append (errmsg, _("No data"));
+		break;
+	default:
+		g_string_append_c (errmsg, (unsigned char) (token->token & 0xff));
+		break;
+	}
+	
+	camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM, errmsg->str);
+	
+	g_string_free (errmsg, TRUE);
+}
+
+
+static struct {
+	const char *name;
+	guint32 flag;
+} imap4_flags[] = {
+	{ "\\Answered",  CAMEL_MESSAGE_ANSWERED     },
+	{ "\\Deleted",   CAMEL_MESSAGE_DELETED      },
+	{ "\\Draft",     CAMEL_MESSAGE_DRAFT        },
+	{ "\\Flagged",   CAMEL_MESSAGE_FLAGGED      },
+	{ "\\Seen",      CAMEL_MESSAGE_SEEN         },
+	{ "\\Recent",    CAMEL_IMAP4_MESSAGE_RECENT },
+	{ "\\*",         CAMEL_MESSAGE_USER         },
+	
+	/* user flags */
+	{ "Junk",        CAMEL_MESSAGE_JUNK         },
+	{ "NonJunk",     0                          },
+};
+
+int
+camel_imap4_parse_flags_list (CamelIMAP4Engine *engine, guint32 *flags, CamelException *ex)
+{
+	camel_imap4_token_t token;
+	guint32 new = 0;
+	int i;
+	
+	if (camel_imap4_engine_next_token (engine, &token, ex) == -1)
+		return -1;
+	
+	if (token.token != '(') {
+		d(fprintf (stderr, "Expected to find a '(' token starting the flags list\n"));
+		camel_imap4_utils_set_unexpected_token_error (ex, engine, &token);
+		return -1;
+	}
+	
+	if (camel_imap4_engine_next_token (engine, &token, ex) == -1)
+		return -1;
+	
+	while (token.token == CAMEL_IMAP4_TOKEN_ATOM || token.token == CAMEL_IMAP4_TOKEN_FLAG) {
+		/* parse the flags list */
+		for (i = 0; i < G_N_ELEMENTS (imap4_flags); i++) {
+			if (!g_ascii_strcasecmp (imap4_flags[i].name, token.v.atom)) {
+				new |= imap4_flags[i].flag;
+				break;
+			}
+		}
+		
+		if (i == G_N_ELEMENTS (imap4_flags))
+			d(fprintf (stderr, "Encountered unknown flag: %s\n", token.v.atom));
+		
+		if (camel_imap4_engine_next_token (engine, &token, ex) == -1)
+			return -1;
+	}
+	
+	if (token.token != ')') {
+		d(fprintf (stderr, "Expected to find a ')' token terminating the flags list\n"));
+		camel_imap4_utils_set_unexpected_token_error (ex, engine, &token);
+		return -1;
+	}
+	
+	*flags = new;
+	
+	return 0;
+}
+
+
+static struct {
+	const char *name;
+	guint32 flag;
+} list_flags[] = {
+	{ "\\Marked",        CAMEL_IMAP4_FOLDER_MARKED    },
+	{ "\\UnMarked",      CAMEL_IMAP4_FOLDER_UNMARKED  },
+	{ "\\NoSelect",      CAMEL_FOLDER_NOSELECT        },
+	{ "\\NoInferiors",   CAMEL_FOLDER_NOINFERIORS     },
+	{ "\\HasChildren",   CAMEL_FOLDER_CHILDREN        },
+	{ "\\HasNoChildren", CAMEL_FOLDER_NOCHILDREN      },
+};
+
+int
+camel_imap4_untagged_list (CamelIMAP4Engine *engine, CamelIMAP4Command *ic, guint32 index, camel_imap4_token_t *token, CamelException *ex)
+{
+	GPtrArray *array = ic->user_data;
+	camel_imap4_list_t *list;
+	unsigned char *buf;
+	guint32 flags = 0;
+	GString *literal;
+	char delim;
+	size_t n;
+	int i;
+	
+	if (camel_imap4_engine_next_token (engine, token, ex) == -1)
+		return -1;
+	
+	/* parse the flag list */
+	if (token->token != '(')
+		goto unexpected;
+	
+	if (camel_imap4_engine_next_token (engine, token, ex) == -1)
+		return -1;
+	
+	while (token->token == CAMEL_IMAP4_TOKEN_FLAG || token->token == CAMEL_IMAP4_TOKEN_ATOM) {
+		for (i = 0; i < G_N_ELEMENTS (list_flags); i++) {
+			if (!g_ascii_strcasecmp (list_flags[i].name, token->v.atom)) {
+				flags |= list_flags[i].flag;
+				break;
+			}
+		}
+		
+		if (camel_imap4_engine_next_token (engine, token, ex) == -1)
+			return -1;
+	}
+	
+	if (token->token != ')')
+		goto unexpected;
+	
+	/* parse the path delimiter */
+	if (camel_imap4_engine_next_token (engine, token, ex) == -1)
+		return -1;
+	
+	switch (token->token) {
+	case CAMEL_IMAP4_TOKEN_NIL:
+		delim = '\0';
+		break;
+	case CAMEL_IMAP4_TOKEN_QSTRING:
+		delim = *token->v.qstring;
+		break;
+	default:
+		goto unexpected;
+	}
+	
+	/* parse the folder name */
+	if (camel_imap4_engine_next_token (engine, token, ex) == -1)
+		return -1;
+	
+	list = g_new (camel_imap4_list_t, 1);
+	list->flags = flags;
+	list->delim = delim;
+	
+	switch (token->token) {
+	case CAMEL_IMAP4_TOKEN_ATOM:
+		list->name = g_strdup (token->v.atom);
+		break;
+	case CAMEL_IMAP4_TOKEN_QSTRING:
+		list->name = g_strdup (token->v.qstring);
+		break;
+	case CAMEL_IMAP4_TOKEN_LITERAL:
+		literal = g_string_new ("");
+		while ((i = camel_imap4_stream_literal (engine->istream, &buf, &n)) == 1)
+			g_string_append_len (literal, buf, n);
+		
+		if (i == -1) {
+			camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+					      _("IMAP server %s unexpectedly disconnected: %s"),
+					      engine->url->host, errno ? g_strerror (errno) : _("Unknown"));
+			g_string_free (literal, TRUE);
+			return -1;
+		}
+		
+		g_string_append_len (literal, buf, n);
+		list->name = literal->str;
+		g_string_free (literal, FALSE);
+		break;
+	default:
+		g_free (list);
+		goto unexpected;
+	}
+	
+	g_ptr_array_add (array, list);
+	
+	return camel_imap4_engine_eat_line (engine, ex);
+	
+ unexpected:
+	
+	camel_imap4_utils_set_unexpected_token_error (ex, engine, token);
+	
+	return -1;
+}
+
+
+static struct {
+	const char *name;
+	int type;
+} imap4_status[] = {
+	{ "MESSAGES",    CAMEL_IMAP4_STATUS_MESSAGES    },
+	{ "RECENT",      CAMEL_IMAP4_STATUS_RECENT      },
+	{ "UIDNEXT",     CAMEL_IMAP4_STATUS_UIDNEXT     },
+	{ "UIDVALIDITY", CAMEL_IMAP4_STATUS_UIDVALIDITY },
+	{ "UNSEEN",      CAMEL_IMAP4_STATUS_UNSEEN      },
+};
+
+
+void
+camel_imap4_status_free (camel_imap4_status_t *status)
+{
+	camel_imap4_status_attr_t *attr, *next;
+	
+	attr = status->attr_list;
+	while (attr != NULL) {
+		next = attr->next;
+		g_free (attr);
+		attr = next;
+	}
+	
+	g_free (status->mailbox);
+	g_free (status);
+}
+
+
+int
+camel_imap4_untagged_status (CamelIMAP4Engine *engine, CamelIMAP4Command *ic, guint32 index, camel_imap4_token_t *token, CamelException *ex)
+{
+	camel_imap4_status_attr_t *attr, *tail, *list = NULL;
+	GPtrArray *array = ic->user_data;
+	camel_imap4_status_t *status;
+	unsigned char *literal;
+	char *mailbox;
+	size_t len;
+	int type;
+	int i;
+	
+	if (camel_imap4_engine_next_token (engine, token, ex) == -1)
+		return -1;
+	
+	switch (token->token) {
+	case CAMEL_IMAP4_TOKEN_ATOM:
+		mailbox = g_strdup (token->v.atom);
+		break;
+	case CAMEL_IMAP4_TOKEN_QSTRING:
+		mailbox = g_strdup (token->v.qstring);
+		break;
+	case CAMEL_IMAP4_TOKEN_LITERAL:
+		if (camel_imap4_engine_literal (engine, &literal, &len, ex) == -1)
+			return -1;
+		
+		mailbox = (char *) literal;
+		break;
+	default:
+		fprintf (stderr, "Unexpected token in IMAP4 untagged STATUS response: %s%c\n",
+			 token->token == CAMEL_IMAP4_TOKEN_NIL ? "NIL" : "",
+			 (unsigned char) (token->token & 0xff));
+		camel_imap4_utils_set_unexpected_token_error (ex, engine, token);
+		return -1;
+	}
+	
+	if (camel_imap4_engine_next_token (engine, token, ex) == -1) {
+		g_free (mailbox);
+		return -1;
+	}
+	
+	if (token->token != '(') {
+		d(fprintf (stderr, "Expected to find a '(' token after the mailbox token in the STATUS response\n"));
+		camel_imap4_utils_set_unexpected_token_error (ex, engine, token);
+		g_free (mailbox);
+		return -1;
+	}
+	
+	if (camel_imap4_engine_next_token (engine, token, ex) == -1) {
+		g_free (mailbox);
+		return -1;
+	}
+	
+	tail = (camel_imap4_status_attr_t *) &list;
+	
+	while (token->token == CAMEL_IMAP4_TOKEN_ATOM) {
+		/* parse the status messages list */
+		type = CAMEL_IMAP4_STATUS_UNKNOWN;
+		for (i = 0; i < G_N_ELEMENTS (imap4_status); i++) {
+			if (!g_ascii_strcasecmp (imap4_status[i].name, token->v.atom)) {
+				type = imap4_status[i].type;
+				break;
+			}
+		}
+		
+		if (type == CAMEL_IMAP4_STATUS_UNKNOWN)
+			fprintf (stderr, "unrecognized token in STATUS list: %s\n", token->v.atom);
+		
+		if (camel_imap4_engine_next_token (engine, token, ex) == -1)
+			goto exception;
+		
+		if (token->token != CAMEL_IMAP4_TOKEN_NUMBER)
+			break;
+		
+		attr = g_new (camel_imap4_status_attr_t, 1);
+		attr->next = NULL;
+		attr->type = type;
+		attr->value = token->v.number;
+		
+		tail->next = attr;
+		tail = attr;
+		
+		if (camel_imap4_engine_next_token (engine, token, ex) == -1)
+			goto exception;
+	}
+	
+	status = g_new (camel_imap4_status_t, 1);
+	status->mailbox = mailbox;
+	status->attr_list = list;
+	list = NULL;
+	
+	g_ptr_array_add (array, status);
+	
+	if (token->token != ')') {
+		d(fprintf (stderr, "Expected to find a ')' token terminating the untagged STATUS response\n"));
+		camel_imap4_utils_set_unexpected_token_error (ex, engine, token);
+		return -1;
+	}
+	
+	if (camel_imap4_engine_next_token (engine, token, ex) == -1)
+		return -1;
+	
+	if (token->token != '\n') {
+		d(fprintf (stderr, "Expected to find a '\\n' token after the STATUS response\n"));
+		camel_imap4_utils_set_unexpected_token_error (ex, engine, token);
+		return -1;
+	}
+	
+	return 0;
+	
+ exception:
+	
+	g_free (mailbox);
+	
+	attr = list;
+	while (attr != NULL) {
+		list = attr->next;
+		g_free (attr);
+		attr = list;
+	}
+	
+	return -1;
+}

Added: trunk/imap4/camel-imap4-utils.h
==============================================================================
--- (empty file)
+++ trunk/imap4/camel-imap4-utils.h	Sat May 10 17:53:05 2008
@@ -0,0 +1,104 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*  Camel
+ *  Copyright (C) 1999-2007 Novell, Inc. (www.novell.com)
+ *
+ *  Authors: Jeffrey Stedfast <fejj novell com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU Lesser General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  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 Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+
+#ifndef __CAMEL_IMAP4_UTILS_H__
+#define __CAMEL_IMAP4_UTILS_H__
+
+#include <glib.h>
+
+#include <camel/camel-exception.h>
+
+G_BEGIN_DECLS
+
+/* IMAP4 flag merging */
+typedef struct {
+	guint32 changed;
+	guint32 bits;
+} flags_diff_t;
+
+void camel_imap4_flags_diff (flags_diff_t *diff, guint32 old, guint32 new);
+guint32 camel_imap4_flags_merge (flags_diff_t *diff, guint32 flags);
+guint32 camel_imap4_merge_flags (guint32 original, guint32 local, guint32 server);
+
+
+struct _CamelIMAP4Engine;
+struct _CamelIMAP4Command;
+struct _CamelFolderSummary;
+struct _camel_imap4_token_t;
+struct _CamelIMAP4StoreSummary;
+struct _CamelIMAP4NamespaceList;
+struct _CamelIMAP4Namespace;
+
+void camel_imap4_namespace_clear (struct _CamelIMAP4Namespace **ns);
+struct _CamelIMAP4NamespaceList *camel_imap4_namespace_list_copy (const struct _CamelIMAP4NamespaceList *nsl);
+void camel_imap4_namespace_list_free (struct _CamelIMAP4NamespaceList *nsl);
+
+char camel_imap4_get_path_delim (struct _CamelIMAP4StoreSummary *s, const char *full_name);
+
+int camel_imap4_get_uid_set (struct _CamelIMAP4Engine *engine, struct _CamelFolderSummary *summary, GPtrArray *infos, int cur, size_t linelen, char **set);
+
+void camel_imap4_utils_set_unexpected_token_error (CamelException *ex, struct _CamelIMAP4Engine *engine, struct _camel_imap4_token_t *token);
+
+int camel_imap4_parse_flags_list (struct _CamelIMAP4Engine *engine, guint32 *flags, CamelException *ex);
+
+/* Note: make sure these don't clash with any bit flags in camel-store.h */
+#define CAMEL_IMAP4_FOLDER_MARKED   (1 << 17)
+#define CAMEL_IMAP4_FOLDER_UNMARKED (1 << 18)
+
+typedef struct {
+	guint32 flags;
+	char delim;
+	char *name;
+} camel_imap4_list_t;
+
+int camel_imap4_untagged_list (struct _CamelIMAP4Engine *engine, struct _CamelIMAP4Command *ic,
+			       guint32 index, struct _camel_imap4_token_t *token, CamelException *ex);
+
+
+enum {
+	CAMEL_IMAP4_STATUS_UNKNOWN,
+	CAMEL_IMAP4_STATUS_MESSAGES,
+	CAMEL_IMAP4_STATUS_RECENT,
+	CAMEL_IMAP4_STATUS_UIDNEXT,
+	CAMEL_IMAP4_STATUS_UIDVALIDITY,
+	CAMEL_IMAP4_STATUS_UNSEEN,
+};
+
+typedef struct _camel_imap4_status_attr {
+	struct _camel_imap4_status_attr *next;
+	guint32 type;
+	guint32 value;
+} camel_imap4_status_attr_t;
+
+typedef struct {
+	camel_imap4_status_attr_t *attr_list;
+	char *mailbox;
+} camel_imap4_status_t;
+
+void camel_imap4_status_free (camel_imap4_status_t *status);
+
+int camel_imap4_untagged_status (struct _CamelIMAP4Engine *engine, struct _CamelIMAP4Command *ic,
+				 guint32 index, struct _camel_imap4_token_t *token, CamelException *ex);
+
+G_END_DECLS
+
+#endif /* __CAMEL_IMAP4_UTILS_H__ */

Added: trunk/imap4/libcamelimap4.urls
==============================================================================
--- (empty file)
+++ trunk/imap4/libcamelimap4.urls	Sat May 10 17:53:05 2008
@@ -0,0 +1 @@
+imap4



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]