aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJeffrey Stedfast <fejj@src.gnome.org>2004-03-25 06:11:05 +0800
committerJeffrey Stedfast <fejj@src.gnome.org>2004-03-25 06:11:05 +0800
commit2ac983181ab03ef7f71cba91201c2d88add302b0 (patch)
treee1e2c971936b06ee28099e93d18c47f083f96202
parent928067b4e75f1d23519e8608d5765a2a2b5d441c (diff)
downloadgsoc2013-evolution-2ac983181ab03ef7f71cba91201c2d88add302b0.tar
gsoc2013-evolution-2ac983181ab03ef7f71cba91201c2d88add302b0.tar.gz
gsoc2013-evolution-2ac983181ab03ef7f71cba91201c2d88add302b0.tar.bz2
gsoc2013-evolution-2ac983181ab03ef7f71cba91201c2d88add302b0.tar.lz
gsoc2013-evolution-2ac983181ab03ef7f71cba91201c2d88add302b0.tar.xz
gsoc2013-evolution-2ac983181ab03ef7f71cba91201c2d88add302b0.tar.zst
gsoc2013-evolution-2ac983181ab03ef7f71cba91201c2d88add302b0.zip
The beginnings of a new IMAP provider module
svn path=/trunk/; revision=25176
-rw-r--r--camel/providers/imap4/Makefile.am33
-rw-r--r--camel/providers/imap4/camel-imap-command.c661
-rw-r--r--camel/providers/imap4/camel-imap-command.h144
-rw-r--r--camel/providers/imap4/camel-imap-engine.c1642
-rw-r--r--camel/providers/imap4/camel-imap-engine.h221
-rw-r--r--camel/providers/imap4/camel-imap-specials.c100
-rw-r--r--camel/providers/imap4/camel-imap-specials.h53
-rw-r--r--camel/providers/imap4/camel-imap-stream.c707
-rw-r--r--camel/providers/imap4/camel-imap-stream.h124
-rw-r--r--camel/providers/imap4/camel-imap-utils.c313
-rw-r--r--camel/providers/imap4/camel-imap-utils.h72
-rw-r--r--camel/providers/imap4/libcamelimap4.urls1
12 files changed, 4071 insertions, 0 deletions
diff --git a/camel/providers/imap4/Makefile.am b/camel/providers/imap4/Makefile.am
new file mode 100644
index 0000000000..c07f943e84
--- /dev/null
+++ b/camel/providers/imap4/Makefile.am
@@ -0,0 +1,33 @@
+## Process this file with automake to produce Makefile.in
+
+libcamelimap4includedir = $(privincludedir)/camel
+
+camel_provider_LTLIBRARIES = libcamelimap4.la
+camel_provider_DATA = libcamelimap4.urls
+
+INCLUDES = \
+ -I$(top_srcdir) \
+ -I$(top_srcdir)/camel \
+ -I$(top_srcdir)/intl \
+ -I$(top_srcdir)/e-util \
+ -I$(top_srcdir) \
+ $(CAMEL_CFLAGS) \
+ $(GNOME_INCLUDEDIR) \
+ $(GTK_INCLUDEDIR) \
+ -DG_LOG_DOMAIN=\"camel-imap4-provider\"
+
+libcamelimap4_la_SOURCES = \
+ camel-imap-command.c \
+ camel-imap-command.h \
+ camel-imap-engine.c \
+ camel-imap-engine.h \
+ camel-imap-specials.c \
+ camel-imap-specials.h \
+ camel-imap-stream.c \
+ camel-imap-stream.h \
+ camel-imap-utils.c \
+ camel-imap-utils.h
+
+libcamelimap4_la_LDFLAGS = -avoid-version -module
+
+EXTRA_DIST = libcamelimap4.urls
diff --git a/camel/providers/imap4/camel-imap-command.c b/camel/providers/imap4/camel-imap-command.c
new file mode 100644
index 0000000000..4dfcb8faee
--- /dev/null
+++ b/camel/providers/imap4/camel-imap-command.c
@@ -0,0 +1,661 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* Camel
+ * Copyright (C) 1999-2004 Jeffrey Stedfast
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+
+#include <camel/camel-stream-null.h>
+#include <camel/camel-stream-filter.h>
+#include <camel/camel-mime-filter-crlf.h>
+
+#include "camel-imap-stream.h"
+#include "camel-imap-engine.h"
+/*#include "camel-imap-folder.h"*/
+#include "camel-imap-specials.h"
+
+#include "camel-imap-command.h"
+
+
+#define d(x) x
+
+
+enum {
+ IMAP_STRING_ATOM,
+ IMAP_STRING_QSTRING,
+ IMAP_STRING_LITERAL,
+};
+
+static int
+imap_string_get_type (const char *str)
+{
+ int type = 0;
+
+ while (*str) {
+ if (!is_atom (*str)) {
+ if (is_qsafe (*str))
+ type = IMAP_STRING_QSTRING;
+ else
+ return IMAP_STRING_LITERAL;
+ }
+ str++;
+ }
+
+ return type;
+}
+
+static gboolean
+imap_string_is_atom_safe (const char *str)
+{
+ while (is_atom (*str))
+ str++;
+
+ return *str == '\0';
+}
+
+static gboolean
+imap_string_is_quote_safe (const char *str)
+{
+ while (is_qsafe (*str))
+ str++;
+
+ return *str == '\0';
+}
+
+static size_t
+camel_imap_literal_length (CamelIMAPLiteral *literal)
+{
+ CamelStream *stream, *null;
+ CamelMimeFilter *crlf;
+ size_t len;
+
+ if (literal->type == CAMEL_IMAP_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_IMAP_LITERAL_STREAM:
+ camel_stream_write_to_stream (literal->literal.stream, stream);
+ camel_stream_reset (literal->literal.stream);
+ break;
+ case CAMEL_IMAP_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 CamelIMAPCommandPart *
+command_part_new (void)
+{
+ CamelIMAPCommandPart *part;
+
+ part = g_new (CamelIMAPCommandPart, 1);
+ part->next = NULL;
+ part->buffer = NULL;
+ part->buflen = 0;
+ part->literal = NULL;
+
+ return part;
+}
+
+static void
+imap_command_append_string (CamelIMAPEngine *engine, CamelIMAPCommandPart **tail, GString *str, const char *string)
+{
+ CamelIMAPCommandPart *part;
+ CamelIMAPLiteral *literal;
+
+ switch (imap_string_get_type (string)) {
+ case IMAP_STRING_ATOM:
+ /* string is safe as it is... */
+ g_string_append (str, string);
+ break;
+ case IMAP_STRING_QSTRING:
+ /* we need to quote the string */
+ /* FIXME: need to escape stuff */
+ g_string_append_printf (str, "\"%s\"", string);
+ break;
+ case IMAP_STRING_LITERAL:
+ if (engine->capa & CAMEL_IMAP_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 (CamelIMAPLiteral, 1);
+ literal->type = CAMEL_IMAP_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;
+ }
+}
+
+CamelIMAPCommand *
+camel_imap_command_newv (CamelIMAPEngine *engine, CamelIMAPFolder *imap_folder, const char *format, va_list args)
+{
+ CamelIMAPCommandPart *parts, *part, *tail;
+ CamelIMAPCommand *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 == '%') {
+ CamelIMAPLiteral *literal;
+ CamelIMAPFolder *folder;
+ CamelDataWrapper *wrapper;
+ CamelStream *stream;
+ unsigned int u;
+ char *string;
+ size_t len;
+ 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':
+ /* CamelIMAPFolder */
+ folder = va_arg (args, CamelIMAPFolder *);
+ string = (char *) camel_imap_folder_utf7_name (folder);
+ imap_command_append_string (engine, &tail, str, string);
+ break;
+ case 'L':
+ /* Literal */
+ literal = va_arg (args, CamelIMAPLiteral *);
+ len = camel_imap_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 'S':
+ /* string which may need to be quoted or made into a literal */
+ string = va_arg (args, char *);
+ imap_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 (CamelIMAPCommand, 1);
+ ((EDListNode *) ic)->next = NULL;
+ ((EDListNode *) ic)->prev = NULL;
+ ic->untagged = g_hash_table_new (g_str_hash, g_str_equal);
+ ic->status = CAMEL_IMAP_COMMAND_QUEUED;
+ ic->resp_codes = g_ptr_array_new ();
+ ic->engine = engine;
+ ic->ref_count = 1;
+ ic->parts = parts;
+ ic->part = parts;
+
+ camel_exception_init (&ic->ex);
+
+ if (imap_folder) {
+ camel_object_ref (imap_folder);
+ ic->folder = imap_folder;
+ } else
+ ic->folder = NULL;
+
+ return ic;
+}
+
+CamelIMAPCommand *
+camel_imap_command_new (CamelIMAPEngine *engine, CamelIMAPFolder *folder, const char *format, ...)
+{
+ CamelIMAPCommand *command;
+ va_list args;
+
+ va_start (args, format);
+ command = camel_imap_command_newv (engine, folder, format, args);
+ va_end (args);
+
+ return command;
+}
+
+void
+camel_imap_command_register_untagged (CamelIMAPCommand *ic, const char *atom, CamelIMAPUntaggedCallback untagged)
+{
+ g_hash_table_insert (ic->untagged, g_strdup (atom), untagged);
+}
+
+void
+camel_imap_command_ref (CamelIMAPCommand *ic)
+{
+ ic->ref_count++;
+}
+
+void
+camel_imap_command_unref (CamelIMAPCommand *ic)
+{
+ CamelIMAPCommandPart *part, *next;
+ CamelIMAPLiteral *literal;
+ 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++) {
+ CamelIMAPRespCode *resp_code;
+
+ resp_code = ic->resp_codes->pdata[i];
+ camel_imap_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_IMAP_LITERAL_STRING:
+ g_free (part->literal->literal.string);
+ break;
+ case CAMEL_IMAP_LITERAL_STREAM:
+ camel_object_unref (part->literal->literal.stream);
+ break;
+ case CAMEL_IMAP_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
+imap_literal_write_to_stream (CamelIMAPLiteral *literal, CamelStream *stream)
+{
+ CamelStream *istream, *ostream = NULL;
+ CamelDataWrapper *wrapper;
+ CamelMimeFilter *crlf;
+ char *string;
+
+ if (literal->type == CAMEL_IMAP_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_IMAP_LITERAL_STREAM:
+ istream = literal->literal.stream;
+ if (camel_stream_write_to_stream (istream, ostream) == -1)
+ goto exception;
+ break;
+ case CAMEL_IMAP_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_imap_token_t *token)
+{
+ switch (token->token) {
+ case CAMEL_IMAP_TOKEN_NO_DATA:
+ fprintf (stderr, "*** NO DATA ***");
+ break;
+ case CAMEL_IMAP_TOKEN_ERROR:
+ fprintf (stderr, "*** ERROR ***");
+ break;
+ case CAMEL_IMAP_TOKEN_NIL:
+ fprintf (stderr, "NIL");
+ break;
+ case CAMEL_IMAP_TOKEN_ATOM:
+ fprintf (stderr, "%s", token->v.atom);
+ break;
+ case CAMEL_IMAP_TOKEN_QSTRING:
+ fprintf (stderr, "\"%s\"", token->v.qstring);
+ break;
+ case CAMEL_IMAP_TOKEN_LITERAL:
+ fprintf (stderr, "{%u}", token->v.literal);
+ break;
+ default:
+ fprintf (stderr, "%c", (unsigned char) (token->token & 0xff));
+ break;
+ }
+}
+
+int
+camel_imap_command_step (CamelIMAPCommand *ic)
+{
+ CamelIMAPEngine *engine = ic->engine;
+ int result = CAMEL_IMAP_RESULT_NONE;
+ CamelIMAPUntaggedCallback untagged;
+ CamelIMAPLiteral *literal;
+ camel_imap_token_t token;
+ unsigned char *linebuf;
+ ssize_t nwritten;
+ size_t len;
+ int ret;
+
+ g_assert (ic->part != NULL);
+
+ if (ic->part == ic->parts) {
+ ic->tag = g_strdup_printf ("%c%0.5u", engine->tagprefix, engine->tag++);
+ camel_stream_printf (engine->ostream, "%s ", ic->tag);
+ d(fprintf (stderr, "sending : %s ", ic->tag));
+ }
+
+#if d(!)0
+ {
+ 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);
+ }
+#endif
+
+ linebuf = ic->part->buffer;
+ len = ic->part->buflen;
+
+ if ((nwritten = camel_stream_write (engine->ostream, linebuf, len)) == -1)
+ goto exception;
+
+ if (camel_stream_flush (engine->ostream) == -1)
+ goto exception;
+
+ /* now we need to read the response(s) from the IMAP server */
+
+ do {
+ if (camel_imap_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_imap_engine_line (engine, &linebuf, &len, &ic->ex) == -1)
+ goto exception;
+
+ if (literal) {
+ if (imap_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_imap_engine_handle_untagged_1 (engine, &token, &ic->ex) == -1)
+ goto exception;
+ } else if (token.token == CAMEL_IMAP_TOKEN_ATOM && !strcmp (token.v.atom, ic->tag)) {
+ /* we got "<tag> OK/NO/BAD" */
+ fprintf (stderr, "got %s response\n", token.v.atom);
+
+ if (camel_imap_engine_next_token (engine, &token, &ic->ex) == -1)
+ goto exception;
+
+ if (token.token == CAMEL_IMAP_TOKEN_ATOM) {
+ if (!strcmp (token.v.atom, "OK"))
+ result = CAMEL_IMAP_RESULT_OK;
+ else if (!strcmp (token.v.atom, "NO"))
+ result = CAMEL_IMAP_RESULT_NO;
+ else if (!strcmp (token.v.atom, "BAD"))
+ result = CAMEL_IMAP_RESULT_BAD;
+
+ if (result == CAMEL_IMAP_RESULT_NONE) {
+ fprintf (stderr, "expected OK/NO/BAD but got %s\n", token.v.atom);
+ goto unexpected;
+ }
+
+ if (camel_imap_engine_next_token (engine, &token, &ic->ex) == -1)
+ goto exception;
+
+ if (token.token == '[') {
+ /* we have a response code */
+ camel_imap_stream_unget_token (engine->istream, &token);
+ if (camel_imap_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_imap_engine_line (engine, NULL, NULL, &ic->ex) == -1)
+ goto exception;
+ }
+ } else {
+ fprintf (stderr, "expected anything but this: ");
+ unexpected_token (&token);
+ fprintf (stderr, "\n");
+
+ goto unexpected;
+ }
+
+ break;
+ } else {
+ fprintf (stderr, "wtf is this: ");
+ unexpected_token (&token);
+ fprintf (stderr, "\n");
+
+ unexpected:
+
+ /* no fucking clue what we got... */
+ if (camel_imap_engine_line (engine, &linebuf, &len, &ic->ex) == -1)
+ goto exception;
+
+ camel_exception_setv (&ic->ex, CAMEL_EXCEPTION_SYSTEM,
+ _("Unexpected response from IMAP server %s: %s"),
+ engine->url->host, linebuf);
+
+ g_free (linebuf);
+
+ goto exception;
+ }
+ } while (1);
+
+ /* status should always be ACTIVE here... */
+ if (ic->status == CAMEL_IMAP_COMMAND_ACTIVE) {
+ ic->part = ic->part->next;
+ if (ic->part == NULL || result) {
+ ic->status = CAMEL_IMAP_COMMAND_COMPLETE;
+ ic->result = result;
+ return 1;
+ }
+ }
+
+ return 0;
+
+ exception:
+
+ ic->status = CAMEL_IMAP_COMMAND_ERROR;
+
+ return -1;
+}
+
+
+void
+camel_imap_command_reset (CamelIMAPCommand *ic)
+{
+ int i;
+
+ for (i = 0; i < ic->resp_codes->len; i++)
+ camel_imap_resp_code_free (ic->resp_codes->pdata[i]);
+ g_ptr_array_set_size (ic->resp_codes, 0);
+
+ ic->status = CAMEL_IMAP_COMMAND_QUEUED;
+ ic->result = CAMEL_IMAP_RESULT_NONE;
+ ic->part = ic->parts;
+ g_free (ic->tag);
+ ic->tag = NULL;
+
+ camel_exception_clear (&ic->ex);
+}
diff --git a/camel/providers/imap4/camel-imap-command.h b/camel/providers/imap4/camel-imap-command.h
new file mode 100644
index 0000000000..af3ad9f927
--- /dev/null
+++ b/camel/providers/imap4/camel-imap-command.h
@@ -0,0 +1,144 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* Camel
+ * Copyright (C) 1999-2004 Jeffrey Stedfast
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+
+#ifndef __CAMEL_IMAP_COMMAND_H__
+#define __CAMEL_IMAP_COMMAND_H__
+
+#include <stdarg.h>
+
+#include <glib.h>
+
+#include <e-util/e-msgport.h>
+
+#include <camel/camel-stream.h>
+#include <camel/camel-exception.h>
+#include <camel/camel-data-wrapper.h>
+
+#ifdef __cplusplus
+extern "C" {
+#pragma }
+#endif /* __cplusplus */
+
+struct _CamelIMAPEngine;
+struct _CamelIMAPFolder;
+struct _camel_imap_token_t;
+
+typedef struct _CamelIMAPCommand CamelIMAPCommand;
+typedef struct _CamelIMAPLiteral CamelIMAPLiteral;
+
+typedef int (* CamelIMAPPlusCallback) (struct _CamelIMAPEngine *engine,
+ CamelIMAPCommand *ic,
+ const unsigned char *linebuf,
+ size_t linelen, CamelException *ex);
+
+typedef int (* CamelIMAPUntaggedCallback) (struct _CamelIMAPEngine *engine,
+ CamelIMAPCommand *ic,
+ guint32 index,
+ struct _camel_imap_token_t *token,
+ CamelException *ex);
+
+enum {
+ CAMEL_IMAP_LITERAL_STRING,
+ CAMEL_IMAP_LITERAL_STREAM,
+ CAMEL_IMAP_LITERAL_WRAPPER,
+};
+
+struct _CamelIMAPLiteral {
+ int type;
+ union {
+ char *string;
+ CamelStream *stream;
+ CamelDataWrapper *wrapper;
+ } literal;
+};
+
+typedef struct _CamelIMAPCommandPart {
+ struct _CamelIMAPCommandPart *next;
+ unsigned char *buffer;
+ size_t buflen;
+
+ CamelIMAPLiteral *literal;
+} CamelIMAPCommandPart;
+
+enum {
+ CAMEL_IMAP_COMMAND_QUEUED,
+ CAMEL_IMAP_COMMAND_ACTIVE,
+ CAMEL_IMAP_COMMAND_COMPLETE,
+ CAMEL_IMAP_COMMAND_ERROR,
+};
+
+enum {
+ CAMEL_IMAP_RESULT_NONE,
+ CAMEL_IMAP_RESULT_OK,
+ CAMEL_IMAP_RESULT_NO,
+ CAMEL_IMAP_RESULT_BAD,
+};
+
+struct _CamelIMAPCommand {
+ EDListNode node;
+
+ struct _CamelIMAPEngine *engine;
+
+ unsigned int ref_count:26;
+ unsigned int status:3;
+ unsigned int result:3;
+ int id;
+
+ char *tag;
+
+ GPtrArray *resp_codes;
+
+ struct _CamelIMAPFolder *folder;
+ CamelException ex;
+
+ /* command parts - logical breaks in the overall command based on literals */
+ CamelIMAPCommandPart *parts;
+
+ /* current part */
+ CamelIMAPCommandPart *part;
+
+ /* untagged handlers */
+ GHashTable *untagged;
+
+ /* '+' callback/data */
+ CamelIMAPPlusCallback plus;
+ void *user_data;
+};
+
+CamelIMAPCommand *camel_imap_command_new (struct _CamelIMAPEngine *engine, struct _CamelIMAPFolder *folder,
+ const char *format, ...);
+CamelIMAPCommand *camel_imap_command_newv (struct _CamelIMAPEngine *engine, struct _CamelIMAPFolder *folder,
+ const char *format, va_list args);
+
+void camel_imap_command_register_untagged (CamelIMAPCommand *ic, const char *atom, CamelIMAPUntaggedCallback untagged);
+
+void camel_imap_command_ref (CamelIMAPCommand *ic);
+void camel_imap_command_unref (CamelIMAPCommand *ic);
+
+/* returns 1 when complete, 0 if there is more to do, or -1 on error */
+int camel_imap_command_step (CamelIMAPCommand *ic);
+
+void camel_imap_command_reset (CamelIMAPCommand *ic);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* __CAMEL_IMAP_COMMAND_H__ */
diff --git a/camel/providers/imap4/camel-imap-engine.c b/camel/providers/imap4/camel-imap-engine.c
new file mode 100644
index 0000000000..36d2e42f9e
--- /dev/null
+++ b/camel/providers/imap4/camel-imap-engine.c
@@ -0,0 +1,1642 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* Camel
+ * Copyright (C) 1999-2004 Jeffrey Stedfast
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+#include <ctype.h>
+#include <errno.h>
+
+#include <camel/camel-sasl.h>
+#include <camel/camel-stream-buffer.h>
+
+#include "camel-imap-command.h"
+#include "camel-imap-stream.h"
+#include "camel-imap-folder.h"
+#include "camel-imap-utils.h"
+
+#include "camel-imap-engine.h"
+
+#define d(x) x
+
+
+static void camel_imap_engine_class_init (CamelIMAPEngineClass *klass);
+static void camel_imap_engine_init (CamelIMAPEngine *engine, CamelIMAPEngineClass *klass);
+static void camel_imap_engine_finalize (CamelObject *object);
+
+
+static CamelObjectClass *parent_class = NULL;
+
+
+CamelType
+camel_imap_engine_get_type (void)
+{
+ static CamelType type = 0;
+
+ if (!type) {
+ type = camel_type_register (CAMEL_TYPE_IMAP_ENGINE,
+ "CamelIMAPEngine",
+ sizeof (CamelIMAPEngine),
+ sizeof (CamelIMAPEngineClass),
+ (CamelObjectClassInitFunc) camel_imap_engine_class_init,
+ NULL,
+ (CamelObjectInitFunc) camel_imap_engine_init,
+ (CamelObjectFinalizeFunc) camel_imap_engine_finalize);
+ }
+
+ return type;
+}
+
+static void
+camel_imap_engine_class_init (CamelIMAPEngineClass *klass)
+{
+ parent_class = camel_type_get_global_classfuncs (CAMEL_OBJECT_TYPE);
+
+ klass->tagprefix = 'A';
+}
+
+static void
+camel_imap_engine_init (CamelIMAPEngine *engine, CamelIMAPEngineClass *klass)
+{
+ engine->state = CAMEL_IMAP_ENGINE_DISCONNECTED;
+ engine->level = CAMEL_IMAP_LEVEL_UNKNOWN;
+
+ engine->session = 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_IMAP_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
+imap_namespace_clear (CamelIMAPNamespace **namespace)
+{
+ CamelIMAPNamespace *node, *next;
+
+ node = *namespace;
+ while (node != NULL) {
+ next = node->next;
+ g_free (node->path);
+ g_free (node);
+ node = next;
+ }
+
+ *namespace = NULL;
+}
+
+static void
+camel_imap_engine_finalize (CamelObject *object)
+{
+ CamelIMAPEngine *engine = (CamelIMAPEngine *) object;
+ EDListNode *node;
+
+ if (engine->session)
+ camel_object_unref (engine->session);
+
+ 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);
+
+ imap_namespace_clear (&engine->namespaces.personal);
+ imap_namespace_clear (&engine->namespaces.other);
+ imap_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_imap_command_unref ((CamelIMAPCommand *) node);
+ }
+}
+
+
+/**
+ * camel_imap_engine_new:
+ * @session: session
+ * @url: service url
+ *
+ * Returns a new imap engine
+ **/
+CamelIMAPEngine *
+camel_imap_engine_new (CamelSession *session, CamelURL *url)
+{
+ CamelIMAPEngine *engine;
+
+ g_return_val_if_fail (CAMEL_IS_SESSION (session), NULL);
+
+ engine = (CamelIMAPEngine *) camel_object_new (CAMEL_TYPE_IMAP_ENGINE);
+ camel_object_ref (session);
+ engine->session = session;
+ engine->url = url;
+
+ return engine;
+}
+
+
+/**
+ * camel_imap_engine_take_stream:
+ * @engine: imap 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_imap_engine_take_stream (CamelIMAPEngine *engine, CamelStream *stream, CamelException *ex)
+{
+ camel_imap_token_t token;
+ int code;
+
+ g_return_val_if_fail (CAMEL_IS_IMAP_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 = (CamelIMAPStream *) camel_imap_stream_new (stream);
+ engine->ostream = camel_stream_buffer_new (stream, CAMEL_STREAM_BUFFER_WRITE);
+ engine->state = CAMEL_IMAP_ENGINE_CONNECTED;
+ camel_object_unref (stream);
+
+ if (camel_imap_engine_next_token (engine, &token, ex) == -1)
+ goto exception;
+
+ if (token.token != '*') {
+ camel_imap_utils_set_unexpected_token_error (ex, engine, &token);
+ goto exception;
+ }
+
+ if ((code = camel_imap_engine_handle_untagged_1 (engine, &token, ex)) == -1) {
+ goto exception;
+ } else if (code != CAMEL_IMAP_UNTAGGED_OK && code != CAMEL_IMAP_UNTAGGED_PREAUTH) {
+ /* FIXME: set an error? */
+ goto exception;
+ }
+
+ return 0;
+
+ exception:
+
+ engine->state = CAMEL_IMAP_ENGINE_DISCONNECTED;
+
+ camel_object_unref (engine->istream);
+ engine->istream = NULL;
+ camel_object_unref (engine->ostream);
+ engine->ostream = NULL;
+
+ return -1;
+}
+
+
+/**
+ * camel_imap_engine_capability:
+ * @engine: IMAP engine
+ * @ex: exception
+ *
+ * Forces the IMAP engine to query the IMAP server for a list of capabilities.
+ *
+ * Returns 0 on success or -1 on fail.
+ **/
+int
+camel_imap_engine_capability (CamelIMAPEngine *engine, CamelException *ex)
+{
+ CamelIMAPCommand *ic;
+ int id, retval = 0;
+
+ ic = camel_imap_engine_queue (engine, NULL, "CAPABILITY\r\n");
+
+ while ((id = camel_imap_engine_iterate (engine)) < ic->id && id != -1)
+ ;
+
+ if (id == -1 || ic->status != CAMEL_IMAP_COMMAND_COMPLETE) {
+ camel_exception_xfer (ex, &ic->ex);
+ retval = -1;
+ }
+
+ camel_imap_command_unref (ic);
+
+ return retval;
+}
+
+
+/**
+ * camel_imap_engine_namespace:
+ * @engine: IMAP engine
+ * @ex: exception
+ *
+ * Forces the IMAP engine to query the IMAP server for a list of namespaces.
+ *
+ * Returns 0 on success or -1 on fail.
+ **/
+int
+camel_imap_engine_namespace (CamelIMAPEngine *engine, CamelException *ex)
+{
+ camel_imap_list_t *list;
+ GPtrArray *array = NULL;
+ CamelIMAPCommand *ic;
+ int id, i;
+
+ if (engine->capa & CAMEL_IMAP_CAPABILITY_NAMESPACE) {
+ ic = camel_imap_engine_queue (engine, NULL, "NAMESPACE\r\n");
+ } else {
+ ic = camel_imap_engine_queue (engine, NULL, "LIST \"\" \"\"\r\n");
+ camel_imap_command_register_untagged (ic, "LIST", camel_imap_untagged_list);
+ ic->user_data = array = g_ptr_array_new ();
+ }
+
+ while ((id = camel_imap_engine_iterate (engine)) < ic->id && id != -1)
+ ;
+
+ if (id == -1 || ic->status != CAMEL_IMAP_COMMAND_COMPLETE) {
+ camel_exception_xfer (ex, &ic->ex);
+ camel_imap_command_unref (ic);
+ return -1;
+ }
+
+ if (array != NULL) {
+ if (ic->result == CAMEL_IMAP_RESULT_OK) {
+ CamelIMAPNamespace *namespace;
+
+ g_assert (array->len == 1);
+ list = array->pdata[0];
+
+ namespace = g_new (CamelIMAPNamespace, 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_imap_command_unref (ic);
+
+ return 0;
+}
+
+
+int
+camel_imap_engine_select_folder (CamelIMAPEngine *engine, CamelFolder *folder, CamelException *ex)
+{
+ CamelIMAPRespCode *resp;
+ CamelIMAPCommand *ic;
+ int id, retval = 0;
+ int i;
+
+ g_return_val_if_fail (CAMEL_IS_IMAP_ENGINE (engine), -1);
+ g_return_val_if_fail (CAMEL_IS_IMAP_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_imap_engine_queue (engine, folder, "SELECT %F\r\n", folder);
+ while ((id = camel_imap_engine_iterate (engine)) < ic->id && id != -1)
+ ;
+
+ if (id == -1 || ic->status != CAMEL_IMAP_COMMAND_COMPLETE) {
+ camel_exception_xfer (ex, &ic->ex);
+ camel_imap_command_unref (ic);
+ return -1;
+ }
+
+ switch (ic->result) {
+ case CAMEL_IMAP_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_IMAP_RESP_CODE_PERM_FLAGS:
+ folder->permanent_flags = resp->v.flags;
+ break;
+ case CAMEL_IMAP_RESP_CODE_READONLY:
+ /*folder->mode = CAMEL_FOLDER_MODE_READ_ONLY;*/
+ break;
+ case CAMEL_IMAP_RESP_CODE_READWRITE:
+ /*folder->mode = CAMEL_FOLDER_MODE_READ_WRITE;*/
+ break;
+ case CAMEL_IMAP_RESP_CODE_UIDNEXT:
+ camel_imap_summary_set_uidnext (folder->summary, resp->v.uidnext);
+ break;
+ case CAMEL_IMAP_RESP_CODE_UIDVALIDITY:
+ camel_imap_summary_set_uidvalidity (folder->summary, resp->v.uidvalidity);
+ break;
+ case CAMEL_IMAP_RESP_CODE_UNSEEN:
+ camel_imap_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_IMAP_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_IMAP_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_imap_command_unref (ic);
+
+ return retval;
+}
+
+
+static struct {
+ const char *name;
+ guint32 flag;
+} imap_capabilities[] = {
+ { "IMAP4", CAMEL_IMAP_CAPABILITY_IMAP4 },
+ { "IMAP4REV1", CAMEL_IMAP_CAPABILITY_IMAP4REV1 },
+ { "STATUS", CAMEL_IMAP_CAPABILITY_STATUS },
+ { "NAMESPACE", CAMEL_IMAP_CAPABILITY_NAMESPACE },
+ { "UIDPLUS", CAMEL_IMAP_CAPABILITY_UIDPLUS },
+ { "LITERAL+", CAMEL_IMAP_CAPABILITY_LITERALPLUS },
+ { "LOGINDISABLED", CAMEL_IMAP_CAPABILITY_LOGINDISABLED },
+ { "STARTTLS", CAMEL_IMAP_CAPABILITY_STARTTLS },
+ { NULL, 0 }
+};
+
+static gboolean
+auth_free (gpointer key, gpointer value, gpointer user_data)
+{
+ g_free (key);
+ return TRUE;
+}
+
+static int
+engine_parse_capability (CamelIMAPEngine *engine, int sentinel, CamelException *ex)
+{
+ camel_imap_token_t token;
+ int i;
+
+ engine->capa = CAMEL_IMAP_CAPABILITY_utf8_search;
+ engine->level = 0;
+
+ g_hash_table_foreach_remove (engine->authtypes, (GHRFunc) auth_free, NULL);
+
+ if (camel_imap_engine_next_token (engine, &token, ex) == -1)
+ return -1;
+
+ while (token.token == CAMEL_IMAP_TOKEN_ATOM) {
+ if (!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; imap_capabilities[i].name; i++) {
+ if (!strcasecmp (imap_capabilities[i].name, token.v.atom))
+ engine->capa |= imap_capabilities[i].flag;
+ }
+ }
+
+ if (camel_imap_engine_next_token (engine, &token, ex) == -1)
+ return -1;
+ }
+
+ if (token.token != sentinel) {
+ camel_imap_utils_set_unexpected_token_error (ex, engine, &token);
+ return -1;
+ }
+
+ /* unget our sentinel token */
+ camel_imap_stream_unget_token (engine->istream, &token);
+
+ /* figure out which version of IMAP we are dealing with */
+ if (engine->capa & CAMEL_IMAP_CAPABILITY_IMAP4REV1) {
+ engine->level = CAMEL_IMAP_LEVEL_IMAP4REV1;
+ engine->capa |= CAMEL_IMAP_CAPABILITY_STATUS;
+ } else if (engine->capa & CAMEL_IMAP_CAPABILITY_IMAP4) {
+ engine->level = CAMEL_IMAP_LEVEL_IMAP4;
+ } else {
+ engine->level = CAMEL_IMAP_LEVEL_UNKNOWN;
+ }
+
+ return 0;
+}
+
+static int
+engine_parse_flags_list (CamelIMAPEngine *engine, CamelIMAPRespCode *resp, int perm, CamelException *ex)
+{
+ guint32 flags = 0;
+
+ if (camel_imap_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 (CamelIMAPEngine *engine, CamelException *ex)
+{
+ camel_imap_token_t token;
+
+ if (engine_parse_flags_list (engine, NULL, FALSE, ex) == -1)
+ return -1;
+
+ if (camel_imap_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_imap_utils_set_unexpected_token_error (ex, engine, &token);
+ return -1;
+ }
+
+ return 0;
+}
+
+
+enum {
+ IMAP_STATUS_MESSAGES,
+ IMAP_STATUS_RECENT,
+ IMAP_STATUS_UIDNEXT,
+ IMAP_STATUS_UIDVALIDITY,
+ IMAP_STATUS_UNSEEN,
+ IMAP_STATUS_UNKNOWN
+};
+
+static struct {
+ const char *name;
+ int type;
+} imap_status[] = {
+ { "MESSAGES", IMAP_STATUS_MESSAGES },
+ { "RECENT", IMAP_STATUS_RECENT },
+ { "UIDNEXT", IMAP_STATUS_UIDNEXT },
+ { "UIDVALIDITY", IMAP_STATUS_UIDVALIDITY },
+ { "UNSEEN", IMAP_STATUS_UNSEEN },
+ { NULL, IMAP_STATUS_UNKNOWN },
+};
+
+static int
+engine_parse_status (CamelIMAPEngine *engine, CamelException *ex)
+{
+ camel_imap_token_t token;
+ char *mailbox;
+ int type;
+
+ if (camel_imap_engine_next_token (engine, &token, ex) == -1)
+ return -1;
+
+ switch (token.token) {
+ case CAMEL_IMAP_TOKEN_ATOM:
+ mailbox = g_strdup (token.v.atom);
+ break;
+ case CAMEL_IMAP_TOKEN_QSTRING:
+ mailbox = g_strdup (token.v.qstring);
+ break;
+ case CAMEL_IMAP_TOKEN_LITERAL:
+ /* FIXME: can this happen? if so implement it */
+ default:
+ fprintf (stderr, "Unexpected token in IMAP untagged STATUS response: %s%c\n",
+ token.token == CAMEL_IMAP_TOKEN_NIL ? "NIL" : "",
+ (unsigned char) (token.token & 0xff));
+ camel_imap_utils_set_unexpected_token_error (ex, engine, &token);
+ return -1;
+ }
+
+ if (camel_imap_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_imap_utils_set_unexpected_token_error (ex, engine, &token);
+ g_free (mailbox);
+ return -1;
+ }
+
+ if (camel_imap_engine_next_token (engine, &token, ex) == -1) {
+ g_free (mailbox);
+ return -1;
+ }
+
+ while (token.token == CAMEL_IMAP_TOKEN_ATOM) {
+ const unsigned char *inptr;
+ unsigned int v = 0;
+
+ /* parse the status messages list */
+ for (type = 0; imap_status[type].name; type++) {
+ if (!strcasecmp (imap_status[type].name, token.v.atom))
+ break;
+ }
+
+ if (type == IMAP_STATUS_UNKNOWN)
+ fprintf (stderr, "unrecognized token in STATUS list: %s\n", token.v.atom);
+
+ if (camel_imap_engine_next_token (engine, &token, ex) == -1) {
+ g_free (mailbox);
+ return -1;
+ }
+
+ if (token.token != CAMEL_IMAP_TOKEN_ATOM)
+ break;
+
+ if (type == IMAP_STATUS_UIDNEXT || type == IMAP_STATUS_UIDVALIDITY) {
+ /* these tokens should be numeric, but we
+ * treat them as strings internally so we are
+ * special-casing them here */
+
+ /* FIXME: save the UIDNEXT/UIDVALIDITY value */
+ } else {
+ inptr = (const unsigned char *) token.v.atom;
+ while (*inptr && isdigit ((int) *inptr) && v < (UINT_MAX / 10))
+ v = (v * 10) + (*inptr++ - '0');
+
+ if (*inptr != '\0') {
+ if (type == IMAP_STATUS_UNKNOWN) {
+ /* we'll let it slide... unget this token and continue */
+ camel_imap_stream_unget_token (engine->istream, &token);
+ goto loop;
+ }
+
+ d(fprintf (stderr, "Encountered non-numeric token after %s in untagged STATUS response: %s\n",
+ imap_status[type].name, token.v.atom));
+ goto loop;
+ }
+
+ switch (type) {
+ case IMAP_STATUS_MESSAGES:
+ /* FIXME: save value */
+ break;
+ case IMAP_STATUS_RECENT:
+ /* FIXME: save value */
+ break;
+ case IMAP_STATUS_UNSEEN:
+ /* FIXME: save value */
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+ }
+
+ loop:
+ if (camel_imap_engine_next_token (engine, &token, ex) == -1) {
+ g_free (mailbox);
+ return -1;
+ }
+ }
+
+ /* don't need this anymore... */
+ g_free (mailbox);
+
+ if (token.token != ')') {
+ d(fprintf (stderr, "Expected to find a ')' token terminating the untagged STATUS response\n"));
+ camel_imap_utils_set_unexpected_token_error (ex, engine, &token);
+ return -1;
+ }
+
+ if (camel_imap_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_imap_utils_set_unexpected_token_error (ex, engine, &token);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int
+engine_parse_namespace (CamelIMAPEngine *engine, CamelException *ex)
+{
+ CamelIMAPNamespace *namespaces[3], *node, *tail;
+ camel_imap_token_t token;
+ int i, n = 0;
+
+ imap_namespace_clear (&engine->namespaces.personal);
+ imap_namespace_clear (&engine->namespaces.other);
+ imap_namespace_clear (&engine->namespaces.shared);
+
+ if (camel_imap_engine_next_token (engine, &token, ex) == -1)
+ return -1;
+
+ do {
+ namespaces[n] = NULL;
+ tail = (CamelIMAPNamespace *) &namespaces[n];
+
+ if (token.token == '(') {
+ /* decode the list of namespace pairs */
+ if (camel_imap_engine_next_token (engine, &token, ex) == -1)
+ goto exception;
+
+ while (token.token == '(') {
+ /* decode a namespace pair */
+
+ /* get the path name token */
+ if (camel_imap_engine_next_token (engine, &token, ex) == -1)
+ goto exception;
+
+ if (token.token != CAMEL_IMAP_TOKEN_QSTRING) {
+ d(fprintf (stderr, "Expected to find a qstring token as first element in NAMESPACE pair\n"));
+ camel_imap_utils_set_unexpected_token_error (ex, engine, &token);
+ goto exception;
+ }
+
+ node = g_new (CamelIMAPNamespace, 1);
+ node->next = NULL;
+ node->path = g_strdup (token.v.qstring);
+
+ /* get the path delimiter token */
+ if (camel_imap_engine_next_token (engine, &token, ex) == -1) {
+ g_free (node->path);
+ g_free (node);
+
+ goto exception;
+ }
+
+ if (token.token != CAMEL_IMAP_TOKEN_QSTRING || strlen (token.v.qstring) > 1) {
+ d(fprintf (stderr, "Expected to find a qstring token as second element in NAMESPACE pair\n"));
+ camel_imap_utils_set_unexpected_token_error (ex, engine, &token);
+ g_free (node->path);
+ g_free (node);
+
+ goto exception;
+ }
+
+ node->sep = *token.v.qstring;
+ 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';
+
+ /* get the closing ')' for this namespace pair */
+ if (camel_imap_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_imap_utils_set_unexpected_token_error (ex, engine, &token);
+
+ goto exception;
+ }
+
+ /* get the next token (should be either '(' or ')') */
+ if (camel_imap_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_imap_utils_set_unexpected_token_error (ex, engine, &token);
+ goto exception;
+ }
+ } else if (token.token == CAMEL_IMAP_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_imap_utils_set_unexpected_token_error (ex, engine, &token);
+ goto exception;
+ }
+
+ /* get the next token (should be either '(', NIL, or '\n') */
+ if (camel_imap_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++)
+ imap_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_imap_resp_code_t code;
+ int save;
+} imap_resp_codes[] = {
+ { "ALERT", CAMEL_IMAP_RESP_CODE_ALERT, 0 },
+ { "BADCHARSET", CAMEL_IMAP_RESP_CODE_BADCHARSET, 0 },
+ { "CAPABILITY", CAMEL_IMAP_RESP_CODE_CAPABILITY, 0 },
+ { "PARSE", CAMEL_IMAP_RESP_CODE_PARSE, 1 },
+ { "PERMANENTFLAGS", CAMEL_IMAP_RESP_CODE_PERM_FLAGS, 1 },
+ { "READ-ONLY", CAMEL_IMAP_RESP_CODE_READONLY, 1 },
+ { "READ-WRITE", CAMEL_IMAP_RESP_CODE_READWRITE, 1 },
+ { "TRYCREATE", CAMEL_IMAP_RESP_CODE_TRYCREATE, 1 },
+ { "UIDNEXT", CAMEL_IMAP_RESP_CODE_UIDNEXT, 1 },
+ { "UIDVALIDITY", CAMEL_IMAP_RESP_CODE_UIDVALIDITY, 1 },
+ { "UNSEEN", CAMEL_IMAP_RESP_CODE_UNSEEN, 1 },
+ { "NEWNAME", CAMEL_IMAP_RESP_CODE_NEWNAME, 1 },
+ { "APPENDUID", CAMEL_IMAP_RESP_CODE_APPENDUID, 1 },
+ { "COPYUID", CAMEL_IMAP_RESP_CODE_COPYUID, 1 },
+ { NULL, CAMEL_IMAP_RESP_CODE_UNKNOWN, 0 }
+};
+
+
+int
+camel_imap_engine_parse_resp_code (CamelIMAPEngine *engine, CamelException *ex)
+{
+ CamelIMAPRespCode *resp = NULL;
+ camel_imap_resp_code_t code;
+ camel_imap_token_t token;
+ unsigned char *linebuf;
+ size_t len;
+
+ if (camel_imap_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_imap_utils_set_unexpected_token_error (ex, engine, &token);
+ return -1;
+ }
+
+ if (camel_imap_engine_next_token (engine, &token, ex) == -1)
+ return -1;
+
+ if (token.token != CAMEL_IMAP_TOKEN_ATOM) {
+ d(fprintf (stderr, "Expected an atom token containing a RESP-CODE\n"));
+ camel_imap_utils_set_unexpected_token_error (ex, engine, &token);
+ return -1;
+ }
+
+ if (token.v.atom[strlen (token.v.atom) - 1] == ']') {
+ /* split this atom ("FOO]") into 2 tokens: "FOO" and "]" */
+ token.token = ']';
+ token.v.atom[strlen (token.v.atom) - 1] = '\0';
+ camel_imap_stream_unget_token (engine->istream, &token);
+ token.token = CAMEL_IMAP_TOKEN_ATOM;
+ }
+
+ for (code = 0; imap_resp_codes[code].name; code++) {
+ if (!strcmp (imap_resp_codes[code].name, token.v.atom)) {
+ if (engine->current && imap_resp_codes[code].save) {
+ resp = g_new0 (CamelIMAPRespCode, 1);
+ resp->code = code;
+ }
+ break;
+ }
+ }
+
+ switch (code) {
+ case CAMEL_IMAP_RESP_CODE_BADCHARSET:
+ /* apparently we don't support UTF-8 afterall */
+ engine->capa &= ~CAMEL_IMAP_CAPABILITY_utf8_search;
+ break;
+ case CAMEL_IMAP_RESP_CODE_CAPABILITY:
+ /* capability list follows */
+ if (engine_parse_capability (engine, ']', ex) == -1)
+ goto exception;
+ break;
+ case CAMEL_IMAP_RESP_CODE_PERM_FLAGS:
+ /* flag list follows */
+ if (engine_parse_flags_list (engine, resp, TRUE, ex) == -1)
+ goto exception;
+ break;
+ case CAMEL_IMAP_RESP_CODE_READONLY:
+ break;
+ case CAMEL_IMAP_RESP_CODE_READWRITE:
+ break;
+ case CAMEL_IMAP_RESP_CODE_TRYCREATE:
+ break;
+ case CAMEL_IMAP_RESP_CODE_UIDNEXT:
+ if (camel_imap_engine_next_token (engine, &token, ex) == -1)
+ goto exception;
+
+ if (token.token != CAMEL_IMAP_TOKEN_NUMBER) {
+ d(fprintf (stderr, "Expected an nz_number token as an argument to the UIDNEXT RESP-CODE\n"));
+ camel_imap_utils_set_unexpected_token_error (ex, engine, &token);
+ goto exception;
+ }
+
+ if (resp != NULL)
+ resp->v.uidnext = token.v.number;
+
+ break;
+ case CAMEL_IMAP_RESP_CODE_UIDVALIDITY:
+ if (camel_imap_engine_next_token (engine, &token, ex) == -1)
+ goto exception;
+
+ if (token.token != CAMEL_IMAP_TOKEN_NUMBER) {
+ d(fprintf (stderr, "Expected an nz_number token as an argument to the UIDVALIDITY RESP-CODE\n"));
+ camel_imap_utils_set_unexpected_token_error (ex, engine, &token);
+ goto exception;
+ }
+
+ if (resp != NULL)
+ resp->v.uidvalidity = token.v.number;
+
+ break;
+ case CAMEL_IMAP_RESP_CODE_UNSEEN:
+ if (camel_imap_engine_next_token (engine, &token, ex) == -1)
+ return -1;
+
+ if (token.token != CAMEL_IMAP_TOKEN_NUMBER) {
+ d(fprintf (stderr, "Expected an nz_number token as an argument to the UNSEEN RESP-CODE\n"));
+ camel_imap_utils_set_unexpected_token_error (ex, engine, &token);
+ goto exception;
+ }
+
+ if (resp != NULL)
+ resp->v.unseen = token.v.number;
+
+ break;
+ case CAMEL_IMAP_RESP_CODE_NEWNAME:
+ /* this RESP-CODE may actually be removed - see here:
+ * http://www.washington.edu/imap/listarch/2001/msg00058.html */
+
+ if (camel_imap_engine_next_token (engine, &token, ex) == -1)
+ return -1;
+
+ if (token.token != CAMEL_IMAP_TOKEN_ATOM && token.token != CAMEL_IMAP_TOKEN_QSTRING) {
+ d(fprintf (stderr, "Expected an atom or qstring token as the first argument to the NEWNAME RESP-CODE\n"));
+ camel_imap_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_IMAP_TOKEN_ATOM && token.token != CAMEL_IMAP_TOKEN_QSTRING) {
+ d(fprintf (stderr, "Expected an atom or qstring token as the second argument to the NEWNAME RESP-CODE\n"));
+ camel_imap_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_IMAP_RESP_CODE_APPENDUID:
+ if (camel_imap_engine_next_token (engine, &token, ex) == -1)
+ return -1;
+
+ if (token.token != CAMEL_IMAP_TOKEN_NUMBER) {
+ d(fprintf (stderr, "Expected an nz_number token as the first argument to the APPENDUID RESP-CODE\n"));
+ camel_imap_utils_set_unexpected_token_error (ex, engine, &token);
+ goto exception;
+ }
+
+ if (resp != NULL)
+ resp->v.appenduid.uidvalidity = token.v.number;
+
+ if (camel_imap_engine_next_token (engine, &token, ex) == -1)
+ return -1;
+
+ if (token.token != CAMEL_IMAP_TOKEN_NUMBER) {
+ d(fprintf (stderr, "Expected an nz_number token as the second argument to the APPENDUID RESP-CODE\n"));
+ camel_imap_utils_set_unexpected_token_error (ex, engine, &token);
+ goto exception;
+ }
+
+ if (resp != NULL)
+ resp->v.appenduid.uid = token.v.number;
+
+ break;
+ case CAMEL_IMAP_RESP_CODE_COPYUID:
+ if (camel_imap_engine_next_token (engine, &token, ex) == -1)
+ return -1;
+
+ if (token.token != CAMEL_IMAP_TOKEN_NUMBER) {
+ d(fprintf (stderr, "Expected an nz_number token as the first argument to the COPYUID RESP-CODE\n"));
+ camel_imap_utils_set_unexpected_token_error (ex, engine, &token);
+ goto exception;
+ }
+
+ if (resp != NULL)
+ resp->v.copyuid.uidvalidity = token.v.number;
+
+ if (camel_imap_engine_next_token (engine, &token, ex) == -1)
+ return -1;
+
+ if (token.token != CAMEL_IMAP_TOKEN_ATOM) {
+ d(fprintf (stderr, "Expected an atom token as the second argument to the COPYUID RESP-CODE\n"));
+ camel_imap_utils_set_unexpected_token_error (ex, engine, &token);
+ goto exception;
+ }
+
+ if (resp != NULL)
+ resp->v.copyuid.srcset = g_strdup (token.v.atom);
+
+ if (camel_imap_engine_next_token (engine, &token, ex) == -1)
+ return -1;
+
+ if (token.token != CAMEL_IMAP_TOKEN_ATOM) {
+ d(fprintf (stderr, "Expected an atom token as the third argument to the APPENDUID RESP-CODE\n"));
+ camel_imap_utils_set_unexpected_token_error (ex, engine, &token);
+ goto exception;
+ }
+
+ if (token.v.atom[strlen (token.v.atom) - 1] == ']') {
+ /* this should be the case, but if not - no big. */
+ token.token = ']';
+ token.v.atom[strlen (token.v.atom) - 1] = '\0';
+ camel_imap_stream_unget_token (engine->istream, &token);
+ token.token = CAMEL_IMAP_TOKEN_ATOM;
+ }
+
+ if (resp != NULL)
+ 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, being careful to check atoms for a trailing ']' */
+ while (token.token != ']' && token.token != '\n') {
+ if (camel_imap_engine_next_token (engine, &token, ex) == -1)
+ goto exception;
+
+ if (token.token == CAMEL_IMAP_TOKEN_ATOM) {
+ if (token.v.atom[strlen (token.v.atom) - 1] == ']') {
+ token.token = ']';
+ break;
+ }
+ }
+ }
+
+ break;
+ }
+
+ while (token.token != ']' && token.token != '\n') {
+ if (camel_imap_engine_next_token (engine, &token, ex) == -1)
+ goto exception;
+ }
+
+ if (token.token != ']') {
+ camel_imap_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_IMAP_RESP_CODE_ALERT) {
+ if (camel_imap_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_IMAP_RESP_CODE_PARSE) {
+ if (camel_imap_engine_line (engine, &linebuf, &len, ex) == -1)
+ goto exception;
+
+ resp->v.parse = linebuf;
+ } else {
+ /* eat up the rest of the response */
+ if (camel_imap_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_imap_resp_code_free (resp);
+
+ return -1;
+}
+
+
+
+/* returns -1 on error, or one of CAMEL_IMAP_UNTAGGED_[OK,NO,BAD,PREAUTH,HANDLED] on success */
+int
+camel_imap_engine_handle_untagged_1 (CamelIMAPEngine *engine, camel_imap_token_t *token, CamelException *ex)
+{
+ int code = CAMEL_IMAP_UNTAGGED_HANDLED;
+ CamelIMAPCommand *ic = engine->current;
+ CamelIMAPUntaggedCallback untagged;
+ CamelFolder *folder;
+ unsigned int v;
+
+ if (camel_imap_engine_next_token (engine, token, ex) == -1)
+ return -1;
+
+ if (token->token == CAMEL_IMAP_TOKEN_ATOM) {
+ if (!strcmp ("BYE", token->v.atom)) {
+ /* we don't care if we fail here, either way we've been disconnected */
+ camel_imap_engine_parse_resp_code (engine, NULL);
+ engine->state = CAMEL_IMAP_ENGINE_DISCONNECTED;
+
+ /* FIXME: emit a "disconnected" signal for our Store?
+ * The Store could then initiate a reconnect if
+ * desirable. */
+
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("IMAP server %s unexpectedly disconnected: %s"),
+ engine->url->host, _("Got BYE response"));
+
+ return -1;
+ } 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_imap_engine_next_token (engine, token, ex) == -1)
+ return -1;
+
+ if (token->token != '\n') {
+ camel_imap_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_IMAP_UNTAGGED_NO : CAMEL_IMAP_UNTAGGED_BAD;
+
+ /* our command has been rejected */
+ if (camel_imap_engine_next_token (engine, token, ex) == -1)
+ return -1;
+
+ if (token->token == '[') {
+ /* we have a resp code */
+ camel_imap_stream_unget_token (engine->istream, token);
+ if (camel_imap_engine_parse_resp_code (engine, ex) == -1)
+ return -1;
+ } else if (token->token != '\n') {
+ /* we just have resp text */
+ if (camel_imap_engine_line (engine, NULL, NULL, ex) == -1)
+ return -1;
+ }
+ } else if (!strcmp ("OK", token->v.atom)) {
+ code = CAMEL_IMAP_UNTAGGED_OK;
+
+ if (engine->state == CAMEL_IMAP_ENGINE_CONNECTED) {
+ /* initial server greeting */
+ engine->state = CAMEL_IMAP_ENGINE_PREAUTH;
+ }
+
+ if (camel_imap_engine_next_token (engine, token, ex) == -1)
+ return -1;
+
+ if (token->token == '[') {
+ /* we have a resp code */
+ camel_imap_stream_unget_token (engine->istream, token);
+ if (camel_imap_engine_parse_resp_code (engine, ex) == -1)
+ return -1;
+ } else {
+ /* we just have resp text */
+ if (camel_imap_engine_line (engine, NULL, NULL, ex) == -1)
+ return -1;
+ }
+ } else if (!strcmp ("PREAUTH", token->v.atom)) {
+ code = CAMEL_IMAP_UNTAGGED_PREAUTH;
+
+ if (engine->state == CAMEL_IMAP_ENGINE_CONNECTED)
+ engine->state = CAMEL_IMAP_ENGINE_AUTHENTICATED;
+
+ if (camel_imap_engine_parse_resp_code (engine, ex) == -1)
+ return -1;
+ } else if (!strcmp ("STATUS", token->v.atom)) {
+ /* FIXME: This should probably be removed... leave it
+ * up to the caller that sent the STATUS command to
+ * register an untagged response handler for this */
+
+ /* next token must be the mailbox name followed by a paren list */
+ if (engine_parse_status (engine, ex) == -1)
+ return -1;
+ } else if (ic && (untagged = g_hash_table_lookup (ic->untagged, token->v.atom))) {
+ /* registered untagged handler for imap 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_imap_engine_eat_line (engine, ex) == -1)
+ return -1;
+ }
+ } else if (token->token == CAMEL_IMAP_TOKEN_NUMBER) {
+ /* we probably have something like "* 1 EXISTS" */
+ v = token->v.number;
+
+ if (camel_imap_engine_next_token (engine, token, ex) == -1)
+ return -1;
+
+ if (token->token != CAMEL_IMAP_TOKEN_ATOM) {
+ camel_imap_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_imap_summary_set_exists (folder->summary, v);
+ } else if (!strcmp ("EXPUNGE", token->v.atom)) {
+ camel_imap_summary_expunge (folder->summary, (int) v);
+ } else if (!strcmp ("RECENT", token->v.atom)) {
+ camel_imap_summary_set_recent (folder->summary, v);
+ } else if (ic && (untagged = g_hash_table_lookup (ic->untagged, token->v.atom))) {
+ /* registered untagged handler for imap 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_imap_engine_eat_line (engine, ex) == -1)
+ return -1;
+ } else {
+ camel_imap_utils_set_unexpected_token_error (ex, engine, token);
+
+ return -1;
+ }
+
+ return code;
+}
+
+
+void
+camel_imap_engine_handle_untagged (CamelIMAPEngine *engine, CamelException *ex)
+{
+ camel_imap_token_t token;
+
+ g_return_if_fail (CAMEL_IS_IMAP_ENGINE (engine));
+
+ do {
+ if (camel_imap_engine_next_token (engine, &token, ex) == -1)
+ goto exception;
+
+ if (token.token != '*')
+ break;
+
+ if (camel_imap_engine_handle_untagged_1 (engine, &token, ex) == -1)
+ goto exception;
+ } while (1);
+
+ camel_imap_stream_unget_token (engine->istream, &token);
+
+ return;
+
+ exception:
+
+ engine->state = CAMEL_IMAP_ENGINE_DISCONNECTED;
+}
+
+
+static int
+imap_process_command (CamelIMAPEngine *engine, CamelIMAPCommand *ic)
+{
+ int retval;
+
+ while ((retval = camel_imap_command_step (ic)) == 0)
+ ;
+
+ if (retval == -1) {
+ engine->state = CAMEL_IMAP_ENGINE_DISCONNECTED;
+ return -1;
+ }
+
+ return 0;
+}
+
+
+static void
+engine_prequeue_folder_select (CamelIMAPEngine *engine)
+{
+ CamelIMAPCommand *ic;
+ const char *cmd;
+
+ ic = (CamelIMAPCommand *) 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_imap_command_new (engine, ic->folder, "SELECT %F\r\n", ic->folder);
+ camel_imap_engine_prequeue (engine, ic);
+ ic->user_data = engine;
+
+ camel_imap_command_unref (ic);
+}
+
+
+static int
+engine_state_change (CamelIMAPEngine *engine, CamelIMAPCommand *ic)
+{
+ const char *cmd;
+ int retval = 0;
+
+ cmd = ic->parts->buffer;
+ if (!strncmp (cmd, "SELECT ", 7) || !strncmp (cmd, "EXAMINE ", 8)) {
+ if (ic->result == CAMEL_IMAP_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_IMAP_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_IMAP_RESULT_OK)
+ engine->state = CAMEL_IMAP_ENGINE_AUTHENTICATED;
+ } else if (!strncmp (cmd, "LOGOUT", 6)) {
+ engine->state = CAMEL_IMAP_ENGINE_DISCONNECTED;
+ }
+
+ return retval;
+}
+
+/**
+ * camel_imap_engine_iterate:
+ * @engine: IMAP 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
+ * CamelIMAPCommand that failed.
+ **/
+int
+camel_imap_engine_iterate (CamelIMAPEngine *engine)
+{
+ CamelIMAPCommand *ic, *nic;
+ GPtrArray *resp_codes;
+ int retval = -1;
+
+ if (e_dlist_empty (&engine->queue))
+ return 0;
+
+ /* check to see if we need to pre-queue a SELECT, if so do it */
+ engine_prequeue_folder_select (engine);
+
+ engine->current = ic = (CamelIMAPCommand *) e_dlist_remhead (&engine->queue);
+ ic->status = CAMEL_IMAP_COMMAND_ACTIVE;
+
+ if (imap_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 imap 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 = (CamelIMAPCommand *) 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_imap_command_unref (ic);
+ ic = nic;
+ }
+
+ retval = ic->id;
+ }
+
+ camel_imap_command_unref (ic);
+
+ return retval;
+}
+
+
+/**
+ * camel_imap_engine_queue:
+ * @engine: IMAP engine
+ * @folder: IMAP folder that the command will affect (or %NULL if it doesn't matter)
+ * @format: command format
+ * @Varargs: arguments
+ *
+ * Basically the same as #camel_imap_command_new() except that this
+ * function also places the command in the engine queue.
+ *
+ * Returns the CamelIMAPCommand.
+ **/
+CamelIMAPCommand *
+camel_imap_engine_queue (CamelIMAPEngine *engine, CamelFolder *folder, const char *format, ...)
+{
+ CamelIMAPCommand *ic;
+ va_list args;
+
+ va_start (args, format);
+ ic = camel_imap_command_newv (engine, (CamelIMAPFolder *) folder, format, args);
+ va_end (args);
+
+ ic->id = engine->nextid++;
+ e_dlist_addtail (&engine->queue, (EDListNode *) ic);
+ camel_imap_command_ref (ic);
+
+ return ic;
+}
+
+
+/**
+ * camel_imap_engine_prequeue:
+ * @engine: IMAP engine
+ * @ic: IMAP command to pre-queue
+ *
+ * Places @ic at the head of the queue of pending IMAP commands.
+ **/
+void
+camel_imap_engine_prequeue (CamelIMAPEngine *engine, CamelIMAPCommand *ic)
+{
+ g_return_if_fail (CAMEL_IS_IMAP_ENGINE (engine));
+ g_return_if_fail (ic != NULL);
+
+ camel_imap_command_ref (ic);
+
+ if (e_dlist_empty (&engine->queue)) {
+ e_dlist_addtail (&engine->queue, (EDListNode *) ic);
+ ic->id = engine->nextid++;
+ } else {
+ CamelIMAPCommand *nic;
+ EDListNode *node;
+
+ node = (EDListNode *) ic;
+ e_dlist_addhead (&engine->queue, node);
+ nic = (CamelIMAPCommand *) node->next;
+ ic->id = nic->id - 1;
+
+ if (ic->id == 0) {
+ /* increment all command ids */
+ node = engine->queue.head;
+ while (node->next) {
+ nic = (CamelIMAPCommand *) node;
+ node = node->next;
+ nic->id++;
+ }
+ }
+ }
+}
+
+
+void
+camel_imap_engine_dequeue (CamelIMAPEngine *engine, CamelIMAPCommand *ic)
+{
+ EDListNode *node = (EDListNode *) ic;
+
+ if (node->next == NULL && node->prev == NULL)
+ return;
+
+ e_dlist_remove (node);
+ node->next = NULL;
+ node->prev = NULL;
+
+ camel_imap_command_unref (ic);
+}
+
+
+int
+camel_imap_engine_next_token (CamelIMAPEngine *engine, camel_imap_token_t *token, CamelException *ex)
+{
+ if (camel_imap_stream_next_token (engine->istream, token) == -1) {
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("IMAP server %s unexpectedly disconnected: %s"),
+ engine->url->host, errno ? g_strerror (errno) : _("Unknown"));
+ return -1;
+ }
+
+ return 0;
+}
+
+
+int
+camel_imap_engine_eat_line (CamelIMAPEngine *engine, CamelException *ex)
+{
+ camel_imap_token_t token;
+ unsigned char *literal;
+ int retval;
+ size_t n;
+
+ do {
+ if (camel_imap_engine_next_token (engine, &token, ex) == -1)
+ return -1;
+
+ if (token.token == CAMEL_IMAP_TOKEN_LITERAL) {
+ while ((retval = camel_imap_stream_literal (engine->istream, &literal, &n)) == 1)
+ ;
+
+ if (retval == -1) {
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("IMAP server %s unexpectedly disconnected: %s"),
+ engine->url->host, errno ? g_strerror (errno) : _("Unknown"));
+
+ return -1;
+ }
+ }
+ } while (token.token != '\n');
+
+ return 0;
+}
+
+
+int
+camel_imap_engine_line (CamelIMAPEngine *engine, unsigned char **line, size_t *len, CamelException *ex)
+{
+ GByteArray *linebuf;
+ unsigned char *buf;
+ size_t buflen;
+ int retval;
+
+ if (line != NULL)
+ linebuf = g_byte_array_new ();
+
+ while ((retval = camel_imap_stream_line (engine->istream, &buf, &buflen)) > 0) {
+ if (line != NULL)
+ g_byte_array_append (linebuf, buf, buflen);
+ }
+
+ if (retval == -1) {
+ camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+ _("IMAP server %s unexpectedly disconnected: %s"),
+ engine->url->host, errno ? g_strerror (errno) : _("Unknown"));
+
+ if (line != NULL)
+ g_byte_array_free (linebuf, TRUE);
+
+ return -1;
+ }
+
+ if (line != NULL) {
+ g_byte_array_append (linebuf, buf, buflen);
+
+ *line = linebuf->data;
+ *len = linebuf->len;
+
+ g_byte_array_free (linebuf, FALSE);
+ }
+
+ return 0;
+}
+
+
+void
+camel_imap_resp_code_free (CamelIMAPRespCode *rcode)
+{
+ switch (rcode->code) {
+ case CAMEL_IMAP_RESP_CODE_PARSE:
+ g_free (rcode->v.parse);
+ break;
+ case CAMEL_IMAP_RESP_CODE_NEWNAME:
+ g_free (rcode->v.newname[0]);
+ g_free (rcode->v.newname[1]);
+ break;
+ case CAMEL_IMAP_RESP_CODE_COPYUID:
+ g_free (rcode->v.copyuid.srcset);
+ g_free (rcode->v.copyuid.destset);
+ break;
+ default:
+ break;
+ }
+
+ g_free (rcode);
+}
diff --git a/camel/providers/imap4/camel-imap-engine.h b/camel/providers/imap4/camel-imap-engine.h
new file mode 100644
index 0000000000..fd125b05d1
--- /dev/null
+++ b/camel/providers/imap4/camel-imap-engine.h
@@ -0,0 +1,221 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* Camel
+ * Copyright (C) 1999-2004 Jeffrey Stedfast
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+
+#ifndef __CAMEL_IMAP_ENGINE_H__
+#define __CAMEL_IMAP_ENGINE_H__
+
+#include <stdarg.h>
+
+#include <glib.h>
+
+#include <e-util/e-msgport.h>
+
+#include <camel/camel-stream.h>
+#include <camel/camel-folder.h>
+#include <camel/camel-session.h>
+
+#ifdef __cplusplus
+extern "C" {
+#pragma }
+#endif /* __cplusplus */
+
+#define CAMEL_TYPE_IMAP_ENGINE (camel_imap_engine_get_type ())
+#define CAMEL_IMAP_ENGINE(obj) (CAMEL_CHECK_CAST ((obj), CAMEL_TYPE_IMAP_ENGINE, CamelIMAPEngine))
+#define CAMEL_IMAP_ENGINE_CLASS(klass) (CAMEL_CHECK_CLASS_CAST ((klass), CAMEL_TYPE_IMAP_ENGINE, CamelIMAPEngineClass))
+#define CAMEL_IS_IMAP_ENGINE(obj) (CAMEL_CHECK_TYPE ((obj), CAMEL_TYPE_IMAP_ENGINE))
+#define CAMEL_IS_IMAP_ENGINE_CLASS(klass) (CAMEL_CHECK_CLASS_TYPE ((klass), CAMEL_TYPE_IMAP_ENGINE))
+#define CAMEL_IMAP_ENGINE_GET_CLASS(obj) (CAMEL_CHECK_GET_CLASS ((obj), CAMEL_TYPE_IMAP_ENGINE, CamelIMAPEngineClass))
+
+typedef struct _CamelIMAPEngine CamelIMAPEngine;
+typedef struct _CamelIMAPEngineClass CamelIMAPEngineClass;
+
+struct _camel_imap_token_t;
+struct _CamelIMAPCommand;
+struct _CamelIMAPFolder;
+struct _CamelIMAPStream;
+
+typedef enum {
+ CAMEL_IMAP_ENGINE_DISCONNECTED,
+ CAMEL_IMAP_ENGINE_CONNECTED,
+ CAMEL_IMAP_ENGINE_PREAUTH,
+ CAMEL_IMAP_ENGINE_AUTHENTICATED,
+ CAMEL_IMAP_ENGINE_SELECTED,
+} camel_imap_engine_t;
+
+typedef enum {
+ CAMEL_IMAP_LEVEL_UNKNOWN,
+ CAMEL_IMAP_LEVEL_IMAP4,
+ CAMEL_IMAP_LEVEL_IMAP4REV1
+} camel_imap_level_t;
+
+enum {
+ CAMEL_IMAP_CAPABILITY_IMAP4 = (1 << 0),
+ CAMEL_IMAP_CAPABILITY_IMAP4REV1 = (1 << 1),
+ CAMEL_IMAP_CAPABILITY_STATUS = (1 << 2),
+ CAMEL_IMAP_CAPABILITY_NAMESPACE = (1 << 3),
+ CAMEL_IMAP_CAPABILITY_UIDPLUS = (1 << 4),
+ CAMEL_IMAP_CAPABILITY_LITERALPLUS = (1 << 5),
+ CAMEL_IMAP_CAPABILITY_LOGINDISABLED = (1 << 6),
+ CAMEL_IMAP_CAPABILITY_STARTTLS = (1 << 7),
+ CAMEL_IMAP_CAPABILITY_useful_lsub = (1 << 8),
+ CAMEL_IMAP_CAPABILITY_utf8_search = (1 << 9),
+};
+
+typedef enum {
+ CAMEL_IMAP_RESP_CODE_ALERT,
+ CAMEL_IMAP_RESP_CODE_BADCHARSET,
+ CAMEL_IMAP_RESP_CODE_CAPABILITY,
+ CAMEL_IMAP_RESP_CODE_PARSE,
+ CAMEL_IMAP_RESP_CODE_PERM_FLAGS,
+ CAMEL_IMAP_RESP_CODE_READONLY,
+ CAMEL_IMAP_RESP_CODE_READWRITE,
+ CAMEL_IMAP_RESP_CODE_TRYCREATE,
+ CAMEL_IMAP_RESP_CODE_UIDNEXT,
+ CAMEL_IMAP_RESP_CODE_UIDVALIDITY,
+ CAMEL_IMAP_RESP_CODE_UNSEEN,
+ CAMEL_IMAP_RESP_CODE_NEWNAME,
+ CAMEL_IMAP_RESP_CODE_APPENDUID,
+ CAMEL_IMAP_RESP_CODE_COPYUID,
+ CAMEL_IMAP_RESP_CODE_UNKNOWN,
+} camel_imap_resp_code_t;
+
+typedef struct _CamelIMAPRespCode {
+ camel_imap_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;
+} CamelIMAPRespCode;
+
+enum {
+ CAMEL_IMAP_UNTAGGED_ERROR = -1,
+ CAMEL_IMAP_UNTAGGED_OK,
+ CAMEL_IMAP_UNTAGGED_NO,
+ CAMEL_IMAP_UNTAGGED_BAD,
+ CAMEL_IMAP_UNTAGGED_PREAUTH,
+ CAMEL_IMAP_UNTAGGED_HANDLED,
+};
+
+typedef struct _CamelIMAPNamespace {
+ struct _CamelIMAPNamespace *next;
+ char *path;
+ char sep;
+} CamelIMAPNamespace;
+
+typedef struct _CamelIMAPNamespaceList {
+ CamelIMAPNamespace *personal;
+ CamelIMAPNamespace *other;
+ CamelIMAPNamespace *shared;
+} CamelIMAPNamespaceList;
+
+enum {
+ CAMEL_IMAP_ENGINE_MAXLEN_LINE,
+ CAMEL_IMAP_ENGINE_MAXLEN_TOKEN
+};
+
+struct _CamelIMAPEngine {
+ CamelObject parent_object;
+
+ CamelSession *session;
+ CamelURL *url;
+
+ camel_imap_engine_t state;
+ camel_imap_level_t level;
+ guint32 capa;
+
+ guint32 maxlen:31;
+ guint32 maxlentype:1;
+
+ CamelIMAPNamespaceList namespaces;
+ GHashTable *authtypes; /* supported authtypes */
+
+ struct _CamelIMAPStream *istream;
+ CamelStream *ostream;
+
+ unsigned char tagprefix; /* 'A'..'Z' */
+ unsigned int tag; /* next command tag */
+ int nextid;
+
+ struct _CamelIMAPFolder *folder; /* currently selected folder */
+
+ EDList queue; /* queue of waiting commands */
+ struct _CamelIMAPCommand *current;
+};
+
+struct _CamelIMAPEngineClass {
+ CamelObjectClass parent_class;
+
+ unsigned char tagprefix;
+};
+
+
+CamelType camel_imap_engine_get_type (void);
+
+CamelIMAPEngine *camel_imap_engine_new (CamelSession *session, CamelURL *url);
+
+/* returns 0 on success or -1 on error */
+int camel_imap_engine_take_stream (CamelIMAPEngine *engine, CamelStream *stream, CamelException *ex);
+
+int camel_imap_engine_capability (CamelIMAPEngine *engine, CamelException *ex);
+int camel_imap_engine_namespace (CamelIMAPEngine *engine, CamelException *ex);
+
+int camel_imap_engine_select_folder (CamelIMAPEngine *engine, CamelFolder *folder, CamelException *ex);
+
+struct _CamelIMAPCommand *camel_imap_engine_queue (CamelIMAPEngine *engine, CamelFolder *folder,
+ const char *format, ...);
+void camel_imap_engine_prequeue (CamelIMAPEngine *engine, struct _CamelIMAPCommand *ic);
+
+void camel_imap_engine_dequeue (CamelIMAPEngine *engine, struct _CamelIMAPCommand *ic);
+
+int camel_imap_engine_iterate (CamelIMAPEngine *engine);
+
+
+/* untagged response utility functions */
+int camel_imap_engine_handle_untagged_1 (CamelIMAPEngine *engine, struct _camel_imap_token_t *token, CamelException *ex);
+void camel_imap_engine_handle_untagged (CamelIMAPEngine *engine, CamelException *ex);
+
+/* stream wrapper utility functions */
+int camel_imap_engine_next_token (CamelIMAPEngine *engine, struct _camel_imap_token_t *token, CamelException *ex);
+int camel_imap_engine_line (CamelIMAPEngine *engine, unsigned char **line, size_t *len, CamelException *ex);
+int camel_imap_engine_eat_line (CamelIMAPEngine *engine, CamelException *ex);
+
+
+/* response code stuff */
+int camel_imap_engine_parse_resp_code (CamelIMAPEngine *engine, CamelException *ex);
+void camel_imap_resp_code_free (CamelIMAPRespCode *rcode);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* __CAMEL_IMAP_ENGINE_H__ */
diff --git a/camel/providers/imap4/camel-imap-specials.c b/camel/providers/imap4/camel-imap-specials.c
new file mode 100644
index 0000000000..5e61d1776b
--- /dev/null
+++ b/camel/providers/imap4/camel-imap-specials.c
@@ -0,0 +1,100 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* Camel
+ * Copyright (C) 1999-2004 Jeffrey Stedfast
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <string.h>
+
+#include "camel-imap-specials.h"
+
+#define CHARS_ATOM_SPECIALS "(){"
+#define CHARS_LWSP " \t\r\n"
+#define CHARS_QUOTED_SPECIALS "\\\""
+#define CHARS_LIST_WILDCARDS "*%"
+
+unsigned char camel_imap_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, 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, 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
+imap_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_imap_specials[vals[i]] |= bit;
+ if (bitcopy) {
+ for (i = 0; i < 256; i++) {
+ if (camel_imap_specials[i] & bitcopy)
+ camel_imap_specials[i] |= bit;
+ }
+ }
+ } else {
+ for (i = 0; i < 256; i++)
+ camel_imap_specials[i] |= bit;
+ for (i = 0; i < len; i++)
+ camel_imap_specials[vals[i]] &= ~bit;
+ if (bitcopy) {
+ for (i = 0; i < 256; i++) {
+ if (camel_imap_specials[i] & bitcopy)
+ camel_imap_specials[i] &= ~bit;
+ }
+ }
+ }
+}
+
+
+void
+camel_imap_specials_init (void)
+{
+ int i;
+
+ for (i = 0; i < 256; i++) {
+ camel_imap_specials[i] = 0;
+ if (i <= 0x1f || i >= 0x7f)
+ camel_imap_specials[i] |= IS_CTRL;
+ }
+
+ camel_imap_specials[' '] |= IS_SPACE;
+
+ imap_init_bits (IS_LWSP, 0, 0, CHARS_LWSP);
+ imap_init_bits (IS_ASPECIAL, 0, 0, CHARS_ATOM_SPECIALS);
+ imap_init_bits (IS_QSPECIAL, 0, 0, CHARS_QUOTED_SPECIALS);
+ imap_init_bits (IS_WILDCARD, 0, 0, CHARS_LIST_WILDCARDS);
+}
diff --git a/camel/providers/imap4/camel-imap-specials.h b/camel/providers/imap4/camel-imap-specials.h
new file mode 100644
index 0000000000..6bae4d3ae3
--- /dev/null
+++ b/camel/providers/imap4/camel-imap-specials.h
@@ -0,0 +1,53 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* Camel
+ * Copyright (C) 1999-2004 Jeffrey Stedfast
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+
+#ifndef __CAMEL_IMAP_SPECIALS_H__
+#define __CAMEL_IMAP_SPECIALS_H__
+
+#ifdef __cplusplus
+extern "C" {
+#pragma }
+#endif /* __cplusplus */
+
+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_imap_specials[256];
+
+#define is_atom(x) ((camel_imap_specials[(unsigned char)(x)] & (IS_ASPECIAL|IS_SPACE|IS_CTRL|IS_WILDCARD|IS_QSPECIAL)) == 0)
+#define is_ctrl(x) ((camel_imap_specials[(unsigned char)(x)] & IS_CTRL) != 0)
+#define is_lwsp(x) ((camel_imap_specials[(unsigned char)(x)] & IS_LWSP) != 0)
+#define is_type(x, t) ((camel_imap_specials[(unsigned char)(x)] & (t)) != 0)
+#define is_qsafe(x) ((camel_imap_specials[(unsigned char)(x)] & (IS_QSPECIAL|IS_CTRL)) == 0)
+#define is_wild(x) ((camel_imap_specials[(unsigned char)(x)] & IS_WILDCARD) != 0)
+
+void camel_imap_specials_init (void);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* __CAMEL_IMAP_SPECIALS_H__ */
diff --git a/camel/providers/imap4/camel-imap-stream.c b/camel/providers/imap4/camel-imap-stream.c
new file mode 100644
index 0000000000..4515a6d144
--- /dev/null
+++ b/camel/providers/imap4/camel-imap-stream.c
@@ -0,0 +1,707 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* Camel
+ * Copyright (C) 1999-2004 Jeffrey Stedfast
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <ctype.h>
+
+#include "camel-imap-specials.h"
+
+#include "camel-imap-stream.h"
+
+#define d(x) x
+
+#define IMAP_TOKEN_LEN 128
+
+static void camel_imap_stream_class_init (CamelIMAPStreamClass *klass);
+static void camel_imap_stream_init (CamelIMAPStream *stream, CamelIMAPStreamClass *klass);
+static void camel_imap_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_imap_stream_get_type (void)
+{
+ static CamelType type = 0;
+
+ if (!type) {
+ type = camel_type_register (CAMEL_TYPE_IMAP_STREAM,
+ "CamelIMAPStream",
+ sizeof (CamelIMAPStream),
+ sizeof (CamelIMAPStreamClass),
+ (CamelObjectClassInitFunc) camel_imap_stream_class_init,
+ NULL,
+ (CamelObjectInitFunc) camel_imap_stream_init,
+ (CamelObjectFinalizeFunc) camel_imap_stream_finalize);
+ }
+
+ return type;
+}
+
+static void
+camel_imap_stream_class_init (CamelIMAPStreamClass *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_imap_stream_init (CamelIMAPStream *imap, CamelIMAPStreamClass *klass)
+{
+ imap->stream = NULL;
+
+ imap->mode = CAMEL_IMAP_STREAM_MODE_TOKEN;
+ imap->disconnected = FALSE;
+ imap->eol = FALSE;
+
+ imap->literal = 0;
+
+ imap->inbuf = imap->realbuf + IMAP_READ_PRELEN;
+ imap->inptr = imap->inbuf;
+ imap->inend = imap->inbuf;
+
+ imap->tokenbuf = g_malloc (IMAP_TOKEN_LEN);
+ imap->tokenptr = imap->tokenbuf;
+ imap->tokenleft = IMAP_TOKEN_LEN;
+
+ imap->unget = NULL;
+}
+
+static void
+camel_imap_stream_finalize (CamelObject *object)
+{
+ CamelIMAPStream *imap = (CamelIMAPStream *) object;
+
+ if (imap->stream)
+ camel_object_unref (imap->stream);
+
+ g_free (imap->tokenbuf);
+ g_free (imap->unget);
+}
+
+
+static ssize_t
+imap_fill (CamelIMAPStream *imap)
+{
+ unsigned char *inbuf, *inptr, *inend;
+ ssize_t nread;
+ size_t inlen;
+
+ if (imap->disconnected) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ inbuf = imap->inbuf;
+ inptr = imap->inptr;
+ inend = imap->inend;
+ inlen = inend - inptr;
+
+ g_assert (inptr <= inend);
+
+ /* attempt to align 'inend' with realbuf + SCAN_HEAD */
+ if (inptr >= inbuf) {
+ inbuf -= inlen < IMAP_READ_PRELEN ? inlen : IMAP_READ_PRELEN;
+ memmove (inbuf, inptr, inlen);
+ inptr = inbuf;
+ inbuf += inlen;
+ } else if (inptr > imap->realbuf) {
+ size_t shift;
+
+ shift = MIN (inptr - imap->realbuf, inend - inbuf);
+ memmove (inptr - shift, inptr, inlen);
+ inptr -= shift;
+ inbuf = inptr + inlen;
+ } else {
+ /* we can't shift... */
+ inbuf = inend;
+ }
+
+ imap->inptr = inptr;
+ imap->inend = inbuf;
+ inend = imap->realbuf + IMAP_READ_PRELEN + IMAP_READ_BUFLEN - 1;
+
+ if ((nread = camel_stream_read (imap->stream, inbuf, inend - inbuf)) == -1)
+ return -1;
+ else if (nread == 0)
+ imap->disconnected = TRUE;
+
+ imap->inend += nread;
+
+ return imap->inend - imap->inptr;
+}
+
+static ssize_t
+stream_read (CamelStream *stream, char *buffer, size_t n)
+{
+ CamelIMAPStream *imap = (CamelIMAPStream *) stream;
+ ssize_t len, nread = 0;
+
+ if (imap->mode == CAMEL_IMAP_STREAM_MODE_LITERAL) {
+ /* don't let our caller read past the end of the literal */
+ n = MIN (n, imap->literal);
+ }
+
+ if (imap->inptr < imap->inend) {
+ len = MIN (n, imap->inend - imap->inptr);
+ memcpy (buffer, imap->inptr, len);
+ imap->inptr += len;
+ nread = len;
+ }
+
+ if (nread < n) {
+ if ((len = camel_stream_read (imap->stream, buffer + nread, n - nread)) == 0)
+ imap->disconnected = TRUE;
+ else if (len == -1)
+ return -1;
+
+ nread += len;
+ }
+
+ if (imap->mode == CAMEL_IMAP_STREAM_MODE_LITERAL) {
+ imap->literal -= nread;
+
+ if (imap->literal == 0) {
+ imap->mode = CAMEL_IMAP_STREAM_MODE_TOKEN;
+ imap->eol = TRUE;
+ }
+ }
+
+ return nread;
+}
+
+static ssize_t
+stream_write (CamelStream *stream, const char *buffer, size_t n)
+{
+ CamelIMAPStream *imap = (CamelIMAPStream *) stream;
+ ssize_t nwritten;
+
+ if (imap->disconnected) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if ((nwritten = camel_stream_write (imap->stream, buffer, n)) == 0)
+ imap->disconnected = TRUE;
+
+ return nwritten;
+}
+
+static int
+stream_flush (CamelStream *stream)
+{
+ CamelIMAPStream *imap = (CamelIMAPStream *) stream;
+
+ return camel_stream_flush (imap->stream);
+}
+
+static int
+stream_close (CamelStream *stream)
+{
+ CamelIMAPStream *imap = (CamelIMAPStream *) stream;
+
+ if (camel_stream_close (imap->stream) == -1)
+ return -1;
+
+ camel_object_unref (imap->stream);
+ imap->stream = NULL;
+
+ imap->disconnected = TRUE;
+
+ return 0;
+}
+
+static gboolean
+stream_eos (CamelStream *stream)
+{
+ CamelIMAPStream *imap = (CamelIMAPStream *) stream;
+
+ if (imap->eol)
+ return TRUE;
+
+ if (imap->disconnected && imap->inptr == imap->inend)
+ return TRUE;
+
+ if (camel_stream_eos (imap->stream))
+ return TRUE;
+
+ return FALSE;
+}
+
+
+/**
+ * camel_imap_stream_new:
+ * @stream: tcp stream
+ *
+ * Returns a new imap stream
+ **/
+CamelStream *
+camel_imap_stream_new (CamelStream *stream)
+{
+ CamelIMAPStream *imap;
+
+ g_return_val_if_fail (CAMEL_IS_STREAM (stream), NULL);
+
+ imap = (CamelIMAPStream *) camel_object_new (CAMEL_TYPE_IMAP_STREAM);
+ camel_object_ref (stream);
+ imap->stream = stream;
+
+ return (CamelStream *) imap;
+}
+
+
+
+#define token_save(imap, start, len) G_STMT_START { \
+ if (imap->tokenleft <= len) { \
+ unsigned int tlen, toff; \
+ \
+ tlen = toff = imap->tokenptr - imap->tokenbuf; \
+ tlen = tlen ? tlen : 1; \
+ \
+ while (tlen < toff + len) \
+ tlen <<= 1; \
+ \
+ imap->tokenbuf = g_realloc (imap->tokenbuf, tlen + 1); \
+ imap->tokenptr = imap->tokenbuf + toff; \
+ imap->tokenleft = tlen - toff; \
+ } \
+ \
+ memcpy (imap->tokenptr, start, len); \
+ imap->tokenptr += len; \
+ imap->tokenleft -= len; \
+} G_STMT_END
+
+#define token_clear(imap) G_STMT_START { \
+ imap->tokenleft += imap->tokenptr - imap->tokenbuf; \
+ imap->tokenptr = imap->tokenbuf; \
+ imap->literal = 0; \
+} G_STMT_END
+
+
+/**
+ * camel_imap_stream_next_token:
+ * @stream: imap stream
+ * @token: imap token
+ *
+ * Reads the next token from the imap stream and saves it in @token.
+ *
+ * Returns 0 on success or -1 on fail.
+ **/
+int
+camel_imap_stream_next_token (CamelIMAPStream *stream, camel_imap_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_IMAP_STREAM (stream), -1);
+ g_return_val_if_fail (stream->mode != CAMEL_IMAP_STREAM_MODE_LITERAL, -1);
+ g_return_val_if_fail (token != NULL, -1);
+
+ if (stream->unget) {
+ memcpy (token, stream->unget, sizeof (camel_imap_token_t));
+ g_free (stream->unget);
+ stream->unget = NULL;
+ return 0;
+ }
+
+ token_clear (stream);
+
+ inptr = stream->inptr;
+ inend = stream->inend;
+ *inend = '\0';
+
+ do {
+ if (inptr == inend) {
+ if ((ret = imap_fill (stream)) < 0) {
+ token->token = CAMEL_IMAP_TOKEN_ERROR;
+ return -1;
+ } else if (ret == 0) {
+ token->token = CAMEL_IMAP_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_IMAP_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 d(!)0
+ if (token->token != '\n')
+ fprintf (stderr, "token: %c\n", token->token);
+ else
+ fprintf (stderr, "token: \\n\n");
+#endif
+ 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 */
+ inptr = strchr (inptr, '\n');
+ }
+
+ /* skip over '\n' */
+ inptr++;
+
+ token->token = CAMEL_IMAP_TOKEN_LITERAL;
+ token->v.literal = literal;
+
+ d(fprintf (stderr, "token: {%u}\n", literal));
+
+ stream->mode = CAMEL_IMAP_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_IMAP_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_IMAP_TOKEN_NIL;
+ d(fprintf (stderr, "token: NIL\n"));
+ } else {
+ token->token = CAMEL_IMAP_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;
+ }
+
+ if ((inptr - start) > 1) {
+ token_save (stream, start, inptr - start);
+
+ /* nul-terminate the flag token */
+ token_save (stream, "", 1);
+
+ token->token = CAMEL_IMAP_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 (imap_fill (stream) <= 0) {
+ token->token = CAMEL_IMAP_TOKEN_ERROR;
+ return -1;
+ }
+
+ inptr = stream->inptr;
+ inend = stream->inend;
+ *inend = '\0';
+ }
+ } while (inptr < inend);
+
+ stream->inptr = inptr;
+
+ return 0;
+}
+
+
+/**
+ * camel_imap_stream_unget_token:
+ * @stream: imap stream
+ * @token: token to 'unget'
+ *
+ * Ungets an imap 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_imap_stream_unget_token (CamelIMAPStream *stream, camel_imap_token_t *token)
+{
+ camel_imap_token_t *unget;
+
+ if (stream->unget)
+ return -1;
+
+ if (token->token != CAMEL_IMAP_TOKEN_NO_DATA) {
+ stream->unget = unget = g_new (camel_imap_token_t, 1);
+ memcpy (unget, token, sizeof (camel_imap_token_t));
+ }
+
+ return 0;
+}
+
+
+/**
+ * camel_imap_stream_readline:
+ * @stream: imap stream
+ * @line: line pointer
+ * @len: line length
+ *
+ * Reads a single line from the imap 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_imap_stream_line (CamelIMAPStream *stream, unsigned char **line, size_t *len)
+{
+ register unsigned char *inptr;
+ unsigned char *inend;
+
+ g_return_val_if_fail (CAMEL_IS_IMAP_STREAM (stream), -1);
+ g_return_val_if_fail (stream->mode != CAMEL_IMAP_STREAM_MODE_LITERAL, -1);
+ g_return_val_if_fail (line != NULL, -1);
+ g_return_val_if_fail (len != NULL, -1);
+
+ if ((stream->inend - stream->inptr) < 3) {
+ /* keep our buffer full to the optimal size */
+ if (imap_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 < inend) {
+ /* got the eoln */
+ if (inptr > stream->inptr && inptr[-1] == '\r')
+ inptr[-1] = '\0';
+ else
+ inptr[0] = '\0';
+
+ stream->inptr = inptr + 1;
+ *len += 1;
+
+ return 0;
+ }
+
+ stream->inptr = inptr;
+
+ return 1;
+}
+
+
+int
+camel_imap_stream_literal (CamelIMAPStream *stream, unsigned char **literal, size_t *len)
+{
+ unsigned char *inptr, *inend;
+ size_t nread;
+
+ g_return_val_if_fail (CAMEL_IS_IMAP_STREAM (stream), -1);
+ g_return_val_if_fail (stream->mode == CAMEL_IMAP_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 (imap_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;
+ if (stream->literal == 0) {
+ stream->mode = CAMEL_IMAP_STREAM_MODE_TOKEN;
+ stream->eol = TRUE;
+ return 0;
+ }
+
+ return 1;
+}
diff --git a/camel/providers/imap4/camel-imap-stream.h b/camel/providers/imap4/camel-imap-stream.h
new file mode 100644
index 0000000000..872cbf7141
--- /dev/null
+++ b/camel/providers/imap4/camel-imap-stream.h
@@ -0,0 +1,124 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* Camel
+ * Copyright (C) 1999-2004 Jeffrey Stedfast
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+
+#ifndef __CAMEL_IMAP_STREAM_H__
+#define __CAMEL_IMAP_STREAM_H__
+
+#include <camel/camel-stream.h>
+
+#ifdef __cplusplus
+extern "C" {
+#pragma }
+#endif /* __cplusplus */
+
+#define CAMEL_TYPE_IMAP_STREAM (camel_imap_stream_get_type ())
+#define CAMEL_IMAP_STREAM(obj) (CAMEL_CHECK_CAST ((obj), CAMEL_TYPE_IMAP_STREAM, CamelIMAPStream))
+#define CAMEL_IMAP_STREAM_CLASS(k) (CAMEL_CHECK_CLASS_CAST ((k), CAMEL_TYPE_IMAP_STREAM, CamelIMAPStreamClass))
+#define CAMEL_IS_IMAP_STREAM(o) (CAMEL_CHECK_TYPE((o), CAMEL_TYPE_IMAP_STREAM))
+
+typedef struct _CamelIMAPStream CamelIMAPStream;
+typedef struct _CamelIMAPStreamClass CamelIMAPStreamClass;
+
+#define IMAP_READ_PRELEN 128
+#define IMAP_READ_BUFLEN 4096
+
+enum {
+ CAMEL_IMAP_TOKEN_NO_DATA = -8,
+ CAMEL_IMAP_TOKEN_ERROR = -7,
+ CAMEL_IMAP_TOKEN_NIL = -6,
+ CAMEL_IMAP_TOKEN_ATOM = -5,
+ CAMEL_IMAP_TOKEN_FLAG = -4,
+ CAMEL_IMAP_TOKEN_NUMBER = -3,
+ CAMEL_IMAP_TOKEN_QSTRING = -2,
+ CAMEL_IMAP_TOKEN_LITERAL = -1,
+ /* CAMEL_IMAP_TOKEN_CHAR would just be the char we got */
+ CAMEL_IMAP_TOKEN_EOLN = '\n',
+ CAMEL_IMAP_TOKEN_LPAREN = '(',
+ CAMEL_IMAP_TOKEN_RPAREN = ')',
+ CAMEL_IMAP_TOKEN_ASTERISK = '*',
+ CAMEL_IMAP_TOKEN_PLUS = '+',
+ CAMEL_IMAP_TOKEN_LBRACKET = '[',
+ CAMEL_IMAP_TOKEN_RBRACKET = ']',
+};
+
+typedef struct _camel_imap_token_t {
+ int token;
+ union {
+ char *atom;
+ char *flag;
+ char *qstring;
+ size_t literal;
+ guint32 number;
+ } v;
+} camel_imap_token_t;
+
+enum {
+ CAMEL_IMAP_STREAM_MODE_TOKEN = 0,
+ CAMEL_IMAP_STREAM_MODE_LITERAL = 1,
+};
+
+struct _CamelIMAPStream {
+ CamelStream parent_object;
+
+ CamelStream *stream;
+
+ guint disconnected:1; /* disconnected state */
+ guint mode:1; /* TOKEN vs LITERAL */
+ guint eol:1; /* end-of-literal */
+
+ size_t literal;
+
+ /* i/o buffers */
+ unsigned char realbuf[IMAP_READ_PRELEN + IMAP_READ_BUFLEN + 1];
+ unsigned char *inbuf;
+ unsigned char *inptr;
+ unsigned char *inend;
+
+ /* token buffers */
+ unsigned char *tokenbuf;
+ unsigned char *tokenptr;
+ unsigned int tokenleft;
+
+ camel_imap_token_t *unget;
+};
+
+struct _CamelIMAPStreamClass {
+ CamelStreamClass parent_class;
+
+ /* Virtual methods */
+};
+
+
+/* Standard Camel function */
+CamelType camel_imap_stream_get_type (void);
+
+CamelStream *camel_imap_stream_new (CamelStream *stream);
+
+int camel_imap_stream_next_token (CamelIMAPStream *stream, camel_imap_token_t *token);
+int camel_imap_stream_unget_token (CamelIMAPStream *stream, camel_imap_token_t *token);
+
+int camel_imap_stream_line (CamelIMAPStream *stream, unsigned char **line, size_t *len);
+int camel_imap_stream_literal (CamelIMAPStream *stream, unsigned char **literal, size_t *len);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* __CAMEL_IMAP_STREAM_H__ */
diff --git a/camel/providers/imap4/camel-imap-utils.c b/camel/providers/imap4/camel-imap-utils.c
new file mode 100644
index 0000000000..129c7b83af
--- /dev/null
+++ b/camel/providers/imap4/camel-imap-utils.c
@@ -0,0 +1,313 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* Camel
+ * Copyright (C) 1999-2004 Jeffrey Stedfast
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+
+#include "camel-imap-engine.h"
+#include "camel-imap-stream.h"
+#include "camel-imap-command.h"
+
+#include "camel-imap-utils.h"
+
+#define d(x) x
+
+
+void
+camel_imap_flags_diff (flags_diff_t *diff, guint32 old, guint32 new)
+{
+ diff->changed = old ^ new;
+ diff->bits = new & diff->changed;
+}
+
+
+guint32
+camel_imap_flags_merge (flags_diff_t *diff, guint32 flags)
+{
+ return (flags & ~diff->changed) | diff->bits;
+}
+
+
+/**
+ * camel_imap_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_imap_merge_flags (guint32 original, guint32 local, guint32 server)
+{
+ flags_diff_t diff;
+
+ camel_imap_flags_diff (&diff, original, local);
+
+ return camel_imap_flags_merge (&diff, server);
+}
+
+
+void
+camel_imap_utils_set_unexpected_token_error (CamelException *ex, CamelIMAPEngine *engine, camel_imap_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_IMAP_TOKEN_NIL:
+ g_string_append (errmsg, "NIL");
+ break;
+ case CAMEL_IMAP_TOKEN_ATOM:
+ g_string_append (errmsg, token->v.atom);
+ break;
+ case CAMEL_IMAP_TOKEN_FLAG:
+ g_string_append (errmsg, token->v.flag);
+ break;
+ case CAMEL_IMAP_TOKEN_QSTRING:
+ g_string_append (errmsg, token->v.qstring);
+ break;
+ case CAMEL_IMAP_TOKEN_LITERAL:
+ g_string_append_printf (errmsg, "{%u}", token->v.literal);
+ break;
+ case CAMEL_IMAP_TOKEN_NUMBER:
+ g_string_append_printf (errmsg, "%u", token->v.number);
+ break;
+ case CAMEL_IMAP_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;
+} imap_flags[] = {
+ { "\\Answered", CAMEL_MESSAGE_ANSWERED },
+ { "\\Deleted", CAMEL_MESSAGE_DELETED },
+ { "\\Draft", CAMEL_MESSAGE_DRAFT },
+ { "\\Flagged", CAMEL_MESSAGE_FLAGGED },
+ { "\\Seen", CAMEL_MESSAGE_SEEN },
+ /*{ "\\Recent", CAMEL_MESSAGE_RECENT },*/
+ { "\\*", CAMEL_MESSAGE_USER },
+};
+
+#if 0
+static struct {
+ const char *name;
+ guint32 flag;
+} imap_user_flags[] = {
+ { "Forwarded", CAMEL_MESSAGE_FORWARDED },
+};
+#endif
+
+
+int
+camel_imap_parse_flags_list (CamelIMAPEngine *engine, guint32 *flags, CamelException *ex)
+{
+ camel_imap_token_t token;
+ guint32 new = 0;
+ int i;
+
+ if (camel_imap_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_imap_utils_set_unexpected_token_error (ex, engine, &token);
+ return -1;
+ }
+
+ if (camel_imap_engine_next_token (engine, &token, ex) == -1)
+ return -1;
+
+ while (token.token == CAMEL_IMAP_TOKEN_ATOM || token.token == CAMEL_IMAP_TOKEN_FLAG) {
+ /* parse the flags list */
+ for (i = 0; i < G_N_ELEMENTS (imap_flags); i++) {
+ if (!strcasecmp (imap_flags[i].name, token.v.atom)) {
+ new |= imap_flags[i].flag;
+ break;
+ }
+ }
+
+#if 0
+ if (i == G_N_ELEMENTS (imap_flags)) {
+ for (i = 0; i < G_N_ELEMENTS (imap_user_flags); i++) {
+ if (!strcasecmp (imap_user_flags[i].name, token.v.atom)) {
+ new |= imap_user_flags[i].flag;
+ break;
+ }
+ }
+
+ if (i == G_N_ELEMENTS (imap_user_flags))
+ fprintf (stderr, "Encountered unknown flag: %s\n", token.v.atom);
+ }
+#else
+ if (i == G_N_ELEMENTS (imap_flags))
+ fprintf (stderr, "Encountered unknown flag: %s\n", token.v.atom);
+#endif
+
+ if (camel_imap_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_imap_utils_set_unexpected_token_error (ex, engine, &token);
+ return -1;
+ }
+
+ *flags = new;
+
+ return 0;
+}
+
+
+struct {
+ const char *name;
+ guint32 flag;
+} list_flags[] = {
+ { "\\Marked", CAMEL_IMAP_FOLDER_MARKED },
+ { "\\Unmarked", CAMEL_IMAP_FOLDER_UNMARKED },
+ { "\\Noselect", CAMEL_IMAP_FOLDER_NOSELECT },
+ { "\\Noinferiors", CAMEL_IMAP_FOLDER_NOINFERIORS },
+ { "\\HasChildren", CAMEL_IMAP_FOLDER_HAS_CHILDREN },
+ { "\\HasNoChildren", CAMEL_IMAP_FOLDER_HAS_NO_CHILDREN },
+};
+
+int
+camel_imap_untagged_list (CamelIMAPEngine *engine, CamelIMAPCommand *ic, guint32 index, camel_imap_token_t *token, CamelException *ex)
+{
+ GPtrArray *array = ic->user_data;
+ camel_imap_list_t *list;
+ unsigned char *buf;
+ guint32 flags = 0;
+ GString *literal;
+ char delim;
+ size_t n;
+ int i;
+
+ if (camel_imap_engine_next_token (engine, token, ex) == -1)
+ return -1;
+
+ /* parse the flag list */
+ if (token->token != '(')
+ goto unexpected;
+
+ if (camel_imap_engine_next_token (engine, token, ex) == -1)
+ return -1;
+
+ while (token->token == CAMEL_IMAP_TOKEN_FLAG || token->token == CAMEL_IMAP_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_imap_engine_next_token (engine, token, ex) == -1)
+ return -1;
+ }
+
+ if (token->token != ')')
+ goto unexpected;
+
+ /* parse the path delimiter */
+ if (camel_imap_engine_next_token (engine, token, ex) == -1)
+ return -1;
+
+ switch (token->token) {
+ case CAMEL_IMAP_TOKEN_NIL:
+ delim = '\0';
+ break;
+ case CAMEL_IMAP_TOKEN_QSTRING:
+ delim = *token->v.qstring;
+ break;
+ default:
+ goto unexpected;
+ }
+
+ /* parse the folder name */
+ if (camel_imap_engine_next_token (engine, token, ex) == -1)
+ return -1;
+
+ list = g_new (camel_imap_list_t, 1);
+ list->flags = flags;
+ list->delim = delim;
+
+ switch (token->token) {
+ case CAMEL_IMAP_TOKEN_ATOM:
+ list->name = g_strdup (token->v.atom);
+ break;
+ case CAMEL_IMAP_TOKEN_QSTRING:
+ list->name = g_strdup (token->v.qstring);
+ break;
+ case CAMEL_IMAP_TOKEN_LITERAL:
+ literal = g_string_new ("");
+ while ((i = camel_imap_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_imap_engine_eat_line (engine, ex);
+
+ unexpected:
+
+ camel_imap_utils_set_unexpected_token_error (ex, engine, token);
+
+ return -1;
+}
diff --git a/camel/providers/imap4/camel-imap-utils.h b/camel/providers/imap4/camel-imap-utils.h
new file mode 100644
index 0000000000..c2f87ae4e1
--- /dev/null
+++ b/camel/providers/imap4/camel-imap-utils.h
@@ -0,0 +1,72 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* Camel
+ * Copyright (C) 1999-2004 Jeffrey Stedfast
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Street #330, Boston, MA 02111-1307, USA.
+ */
+
+
+#ifndef __CAMEL_IMAP_UTILS_H__
+#define __CAMEL_IMAP_UTILS_H__
+
+#include <glib.h>
+
+#ifdef __cplusplus
+extern "C" {
+#pragma }
+#endif /* __cplusplus */
+
+/* IMAP flag merging */
+typedef struct {
+ guint32 changed;
+ guint32 bits;
+} flags_diff_t;
+
+void camel_imap_flags_diff (flags_diff_t *diff, guint32 old, guint32 new);
+guint32 camel_imap_flags_merge (flags_diff_t *diff, guint32 flags);
+guint32 camel_imap_merge_flags (guint32 original, guint32 local, guint32 server);
+
+
+struct _CamelIMAPEngine;
+struct _CamelIMAPCommand;
+struct _camel_imap_token_t;
+
+void camel_imap_utils_set_unexpected_token_error (CamelException *ex, struct _CamelIMAPEngine *engine, struct _camel_imap_token_t *token);
+
+int camel_imap_parse_flags_list (struct _CamelIMAPEngine *engine, guint32 *flags, CamelException *ex);
+
+enum {
+ CAMEL_IMAP_FOLDER_MARKED = (1 << 0),
+ CAMEL_IMAP_FOLDER_UNMARKED = (1 << 1),
+ CAMEL_IMAP_FOLDER_NOSELECT = (1 << 2),
+ CAMEL_IMAP_FOLDER_NOINFERIORS = (1 << 3),
+ CAMEL_IMAP_FOLDER_HAS_CHILDREN = (1 << 4),
+ CAMEL_IMAP_FOLDER_HAS_NO_CHILDREN = (1 << 5),
+};
+
+typedef struct {
+ guint32 flags;
+ char delim;
+ char *name;
+} camel_imap_list_t;
+
+int camel_imap_untagged_list (struct _CamelIMAPEngine *engine, struct _CamelIMAPCommand *ic,
+ guint32 index, struct _camel_imap_token_t *token, CamelException *ex);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* __CAMEL_IMAP_UTILS_H__ */
diff --git a/camel/providers/imap4/libcamelimap4.urls b/camel/providers/imap4/libcamelimap4.urls
new file mode 100644
index 0000000000..7ccb0b0414
--- /dev/null
+++ b/camel/providers/imap4/libcamelimap4.urls
@@ -0,0 +1 @@
+imap4