From 2ac983181ab03ef7f71cba91201c2d88add302b0 Mon Sep 17 00:00:00 2001 From: Jeffrey Stedfast Date: Wed, 24 Mar 2004 22:11:05 +0000 Subject: The beginnings of a new IMAP provider module svn path=/trunk/; revision=25176 --- camel/providers/imap4/Makefile.am | 33 + camel/providers/imap4/camel-imap-command.c | 661 +++++++++++ camel/providers/imap4/camel-imap-command.h | 144 +++ camel/providers/imap4/camel-imap-engine.c | 1642 +++++++++++++++++++++++++++ camel/providers/imap4/camel-imap-engine.h | 221 ++++ camel/providers/imap4/camel-imap-specials.c | 100 ++ camel/providers/imap4/camel-imap-specials.h | 53 + camel/providers/imap4/camel-imap-stream.c | 707 ++++++++++++ camel/providers/imap4/camel-imap-stream.h | 124 ++ camel/providers/imap4/camel-imap-utils.c | 313 +++++ camel/providers/imap4/camel-imap-utils.h | 72 ++ camel/providers/imap4/libcamelimap4.urls | 1 + 12 files changed, 4071 insertions(+) create mode 100644 camel/providers/imap4/Makefile.am create mode 100644 camel/providers/imap4/camel-imap-command.c create mode 100644 camel/providers/imap4/camel-imap-command.h create mode 100644 camel/providers/imap4/camel-imap-engine.c create mode 100644 camel/providers/imap4/camel-imap-engine.h create mode 100644 camel/providers/imap4/camel-imap-specials.c create mode 100644 camel/providers/imap4/camel-imap-specials.h create mode 100644 camel/providers/imap4/camel-imap-stream.c create mode 100644 camel/providers/imap4/camel-imap-stream.h create mode 100644 camel/providers/imap4/camel-imap-utils.c create mode 100644 camel/providers/imap4/camel-imap-utils.h create mode 100644 camel/providers/imap4/libcamelimap4.urls (limited to 'camel/providers') 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 +#endif + +#include +#include +#include + +#include +#include +#include + +#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 " 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 " 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 + +#include + +#include + +#include +#include +#include + +#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 +#endif + +#include +#include +#include + +#include +#include + +#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*] + **/ + +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*] "]" */ + + /* 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 + +#include + +#include + +#include +#include +#include + +#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 +#endif + +#include + +#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 +#endif + +#include +#include +#include +#include + +#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 + +#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 +#endif + +#include +#include +#include + +#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 + +#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 -- cgit v1.2.3