Changed name from "NT Login" to simply "Login".
authorNot Zed <NotZed@Ximian.com>
Wed, 30 Jan 2002 05:14:48 +0000 (05:14 +0000)
committerMichael Zucci <zucchi@src.gnome.org>
Wed, 30 Jan 2002 05:14:48 +0000 (05:14 +0000)
2002-01-30  Not Zed  <NotZed@Ximian.com>

        * camel-sasl-login.c: Changed name from "NT Login" to simply
        "Login".

        * providers/pop3/*: Entirely new pop implmentation, supporting
        pipelining.

2002-01-29  Not Zed  <NotZed@Ximian.com>

        * camel-data-cache.c (free_busy): We dont want to unref the
        stream, instead, stop listening to the finalised events, and free
        the path only.

2002-01-25  Not Zed  <NotZed@Ximian.com>

        * camel-data-cache.c (stream_finalised): Remove the object from
        the busy_stream hashtable, not the busy_path hashtable.

14 files changed:
camel/ChangeLog
camel/camel-data-cache.c
camel/camel-sasl-login.c
camel/camel-session.c
camel/providers/pop3/Makefile.am
camel/providers/pop3/camel-pop3-engine.c [new file with mode: 0644]
camel/providers/pop3/camel-pop3-engine.h [new file with mode: 0644]
camel/providers/pop3/camel-pop3-folder.c
camel/providers/pop3/camel-pop3-folder.h
camel/providers/pop3/camel-pop3-provider.c
camel/providers/pop3/camel-pop3-store.c
camel/providers/pop3/camel-pop3-store.h
camel/providers/pop3/camel-pop3-stream.c [new file with mode: 0644]
camel/providers/pop3/camel-pop3-stream.h [new file with mode: 0644]

index 29be96a..80300e6 100644 (file)
@@ -1,3 +1,22 @@
+2002-01-30  Not Zed  <NotZed@Ximian.com>
+
+       * camel-sasl-login.c: Changed name from "NT Login" to simply
+       "Login".
+
+       * providers/pop3/*: Entirely new pop implmentation, supporting
+       pipelining.
+
+2002-01-29  Not Zed  <NotZed@Ximian.com>
+
+       * camel-data-cache.c (free_busy): We dont want to unref the
+       stream, instead, stop listening to the finalised events, and free
+       the path only.
+
+2002-01-25  Not Zed  <NotZed@Ximian.com>
+
+       * camel-data-cache.c (stream_finalised): Remove the object from
+       the busy_stream hashtable, not the busy_path hashtable.
+
 2002-01-29  Jeffrey Stedfast  <fejj@ximian.com>
 
        * providers/imap/camel-imap-folder.c (imap_update_summary): Added
        Modify the interface so that we can limit the size of the uid set
        string returned.
 
+>>>>>>> 1.1366
 2002-01-14  Not Zed  <NotZed@Ximian.com>
 
        * providers/imap/camel-imap-search.c (imap_body_contains):
index 5fa7a40..c2b8ac8 100644 (file)
@@ -41,6 +41,9 @@
 
 extern int camel_verbose_debug;
 #define dd(x) (camel_verbose_debug?(x):0)
+#define d(x)
+
+static void stream_finalised(CamelObject *o, void *event_data, void *data);
 
 /* how many 'bits' of hash are used to key the toplevel directory */
 #define CAMEL_DATA_CACHE_BITS (6)
@@ -97,8 +100,10 @@ static void data_cache_init(CamelDataCache *cdc, CamelDataCacheClass *klass)
 }
 
 static void
-free_busy(CamelStream *stream, char *path, void *data)
+free_busy(CamelStream *stream, char *path, CamelDataCache *cdc)
 {
+       d(printf(" Freeing busy stream %p path %s\n", stream, path));
+       camel_object_unhook_event((CamelObject *)stream, "finalize", stream_finalised, cdc);
        camel_object_unref((CamelObject *)stream);
        g_free(path);
 }
@@ -109,7 +114,9 @@ static void data_cache_finalise(CamelDataCache *cdc)
 
        p = cdc->priv;
 
-       g_hash_table_foreach(p->busy_stream, (GHFunc)free_busy, NULL);
+       d(printf("cache finalised, %d (= %d?) streams reamining\n", g_hash_table_size(p->busy_stream), g_hash_table_size(p->busy_path)));
+
+       g_hash_table_foreach(p->busy_stream, (GHFunc)free_busy, cdc);
        g_hash_table_destroy(p->busy_path);
        g_hash_table_destroy(p->busy_stream);
 
@@ -294,12 +301,17 @@ stream_finalised(CamelObject *o, void *event_data, void *data)
        CamelDataCache *cdc = data;
        char *key;
 
+       d(printf("Stream finalised '%p'\n", data));
+
        CDC_LOCK(cdc, lock);
        key = g_hash_table_lookup(cdc->priv->busy_stream, o);
        if (key) {
+               d(printf("  For path '%s'\n", key));
                g_hash_table_remove(cdc->priv->busy_path, key);
-               g_hash_table_remove(cdc->priv->busy_path, o);
+               g_hash_table_remove(cdc->priv->busy_stream, o);
                g_free(key);
+       } else {
+               d(printf("  Unknown stream?!\n"));
        }
        CDC_UNLOCK(cdc, lock);
 }
index 02e78d5..f6c3c9e 100644 (file)
@@ -29,7 +29,7 @@
 #include "camel-service.h"
 
 CamelServiceAuthType camel_sasl_login_authtype = {
-       N_("NT Login"),
+       N_("Login"),
        
        N_("This option will connect to the server using a "
           "simple password."),
index bcd6802..93e6fea 100644 (file)
@@ -385,7 +385,7 @@ service_cache_remove (CamelService *service, gpointer event_data, gpointer user_
        g_return_if_fail (CAMEL_IS_SESSION (session));
        g_return_if_fail (service != NULL);
        g_return_if_fail (service->url != NULL);
-       
+
        CAMEL_SESSION_LOCK(session, lock);
 
        provider = g_hash_table_lookup (session->providers, service->url->protocol);
index cde7baf..4ce92ea 100644 (file)
@@ -20,12 +20,16 @@ INCLUDES =                                  \
        -DG_LOG_DOMAIN=\"camel-pop3-provider\"
 
 libcamelpop3_la_SOURCES =                      \
+       camel-pop3-engine.c                     \
        camel-pop3-folder.c                     \
        camel-pop3-provider.c                   \
+       camel-pop3-stream.c                     \
        camel-pop3-store.c
 
 libcamelpop3include_HEADERS =                  \
+       camel-pop3-engine.h                     \
        camel-pop3-folder.h                     \
+       camel-pop3-stream.h                     \
        camel-pop3-store.h
 
 
diff --git a/camel/providers/pop3/camel-pop3-engine.c b/camel/providers/pop3/camel-pop3-engine.c
new file mode 100644 (file)
index 0000000..22d3ab5
--- /dev/null
@@ -0,0 +1,348 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; fill-column: 160 -*-
+ *
+ * Author:
+ *  Michael Zucchi <notzed@ximian.com>
+ *
+ * Copyright 1999, 2000 Ximian, Inc. (www.ximian.com)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <errno.h>
+
+#include <string.h>
+#include <stdio.h>
+
+#include <glib.h>
+
+#include "camel-pop3-engine.h"
+#include "camel-pop3-stream.h"
+#include <camel/camel-service.h>
+#include <camel/camel-sasl.h>
+
+/* max 'outstanding' bytes in output stream, so we can't deadlock waiting
+   for the server to accept our data when pipelining */
+#define CAMEL_POP3_SEND_LIMIT (1024)
+
+
+extern int camel_verbose_debug;
+#define dd(x) (camel_verbose_debug?(x):0)
+
+static void get_capabilities(CamelPOP3Engine *pe);
+
+static CamelObjectClass *parent_class = NULL;
+
+/* Returns the class for a CamelStream */
+#define CS_CLASS(so) CAMEL_POP3_ENGINE_CLASS(CAMEL_OBJECT_GET_CLASS(so))
+
+static void
+camel_pop3_engine_class_init (CamelPOP3EngineClass *camel_pop3_engine_class)
+{
+       parent_class = camel_type_get_global_classfuncs( CAMEL_OBJECT_TYPE );
+}
+
+static void
+camel_pop3_engine_init(CamelPOP3Engine *pe, CamelPOP3EngineClass *peclass)
+{
+       e_dlist_init(&pe->active);
+       e_dlist_init(&pe->queue);
+       e_dlist_init(&pe->done);
+       pe->state = CAMEL_POP3_ENGINE_DISCONNECT;
+}
+
+static void
+camel_pop3_engine_finalise(CamelPOP3Engine *pe)
+{
+       /* FIXME: Also flush/free any outstanding requests, etc */
+
+       if (pe->stream)
+               camel_object_unref((CamelObject *)pe->stream);
+}
+
+CamelType
+camel_pop3_engine_get_type (void)
+{
+       static CamelType camel_pop3_engine_type = CAMEL_INVALID_TYPE;
+
+       if (camel_pop3_engine_type == CAMEL_INVALID_TYPE) {
+               camel_pop3_engine_type = camel_type_register(camel_object_get_type(),
+                                                            "CamelPOP3Engine",
+                                                            sizeof( CamelPOP3Engine ),
+                                                            sizeof( CamelPOP3EngineClass ),
+                                                            (CamelObjectClassInitFunc) camel_pop3_engine_class_init,
+                                                            NULL,
+                                                            (CamelObjectInitFunc) camel_pop3_engine_init,
+                                                            (CamelObjectFinalizeFunc) camel_pop3_engine_finalise );
+       }
+
+       return camel_pop3_engine_type;
+}
+
+/**
+ * camel_pop3_engine_new:
+ *
+ * Returns a NULL stream.  A null stream is always at eof, and
+ * always returns success for all reads and writes.
+ *
+ * Return value: the stream
+ **/
+CamelPOP3Engine *
+camel_pop3_engine_new(CamelStream *source)
+{
+       CamelPOP3Engine *pe;
+
+       pe = (CamelPOP3Engine *)camel_object_new(camel_pop3_engine_get_type ());
+
+       pe->stream = (CamelPOP3Stream *)camel_pop3_stream_new(source);
+       pe->state = CAMEL_POP3_ENGINE_AUTH;
+
+       get_capabilities(pe);
+
+       return pe;
+}
+
+/* TODO: read implementation too?
+   STARTLS?
+   etc? */
+struct {
+       char *cap;
+       guint32 flag;
+} capa[] = {
+       { "APOP" , CAMEL_POP3_CAP_APOP },
+       { "TOP" , CAMEL_POP3_CAP_TOP },
+       { "UIDL", CAMEL_POP3_CAP_UIDL },
+       { "PIPELINING", CAMEL_POP3_CAP_PIPE },
+};
+
+static void
+cmd_capa(CamelPOP3Engine *pe, CamelPOP3Stream *stream, void *data)
+{
+       unsigned char *line, *tok, *next;
+       unsigned int len;
+       int ret;
+       int i;
+       CamelServiceAuthType *auth;
+
+       dd(printf("cmd_capa\n"));
+
+       do {
+               ret = camel_pop3_stream_line(stream, &line, &len);
+               if (ret >= 0) {
+                       if (strncmp(line, "SASL ", 5) == 0) {
+                               tok = line+5;
+                               dd(printf("scanning tokens '%s'\n", tok));
+                               while (tok) {
+                                       next = strchr(tok, ' ');
+                                       if (next)
+                                               *next++ = 0;
+                                       auth = camel_sasl_authtype(tok);
+                                       if (auth) {
+                                               dd(printf("got auth type '%s'\n", tok));
+                                               pe->auth = g_list_prepend(pe->auth, auth);
+                                       } else {
+                                               dd(printf("unsupported auth type '%s'\n", tok));
+                                       }
+                                       tok = next;
+                               }
+                       } else {
+                               for (i=0;i<sizeof(capa)/sizeof(capa[0]);i++) {
+                                       if (strcmp(capa[i].cap, line) == 0)
+                                               pe->capa |= capa[i].flag;
+                               }
+                       }
+               }
+       } while (ret>0);
+}
+
+static void
+get_capabilities(CamelPOP3Engine *pe)
+{
+       CamelPOP3Command *pc;
+       unsigned char *line, *apop, *apopend;
+       unsigned int len;
+       extern CamelServiceAuthType camel_pop3_password_authtype;
+       extern CamelServiceAuthType camel_pop3_apop_authtype;
+
+       /* first, read the greeting */
+       if (camel_pop3_stream_line(pe->stream, &line, &len) == -1
+           || strncmp(line, "+OK", 3) != 0)
+               return;
+
+       if ((apop = strchr(line+3, '<'))
+           && (apopend = strchr(apop, '>'))) {
+               *apopend = 0;
+               pe->apop = g_strdup(apop+1);
+               pe->capa = CAMEL_POP3_CAP_APOP;
+               pe->auth = g_list_append(pe->auth, &camel_pop3_apop_authtype);
+       }
+
+       pe->auth = g_list_prepend(pe->auth, &camel_pop3_password_authtype);
+
+       pc = camel_pop3_engine_command_new(pe, CAMEL_POP3_COMMAND_MULTI, cmd_capa, NULL, "CAPA\r\n");
+       while (camel_pop3_engine_iterate(pe, pc) > 0)
+               ;
+       camel_pop3_engine_command_free(pe, pc);
+}
+
+/* returns true if the command was sent, false if it was just queued */
+static int
+engine_command_queue(CamelPOP3Engine *pe, CamelPOP3Command *pc)
+{
+       if (((pe->capa & CAMEL_POP3_CAP_PIPE) == 0 || (pe->sentlen + strlen(pc->data)) > CAMEL_POP3_SEND_LIMIT)
+           && pe->current != NULL) {
+               e_dlist_addtail(&pe->queue, (EDListNode *)pc);
+               return FALSE;
+       } else {
+               /* ??? */
+               if (camel_stream_write((CamelStream *)pe->stream, pc->data, strlen(pc->data)) == -1) {
+                       e_dlist_addtail(&pe->queue, (EDListNode *)pc);
+                       return FALSE;
+               }
+
+               pe->sentlen += strlen(pc->data);
+
+               pc->state = CAMEL_POP3_COMMAND_DISPATCHED;
+
+               if (pe->current == NULL)
+                       pe->current = pc;
+               else
+                       e_dlist_addtail(&pe->active, (EDListNode *)pc);
+
+               return TRUE;
+       }
+}
+
+/* returns -1 on error (sets errno), 0 when no work to do, or >0 if work remaining */
+int
+camel_pop3_engine_iterate(CamelPOP3Engine *pe, CamelPOP3Command *pcwait)
+{
+       unsigned char *p;
+       unsigned int len;
+       CamelPOP3Command *pc, *pw, *pn;
+
+       if (pcwait && pcwait->state >= CAMEL_POP3_COMMAND_OK)
+               return 0;
+
+       pc = pe->current;
+       if (pc == NULL)
+               return 0;
+
+       /* LOCK */
+
+       if (camel_pop3_stream_line(pe->stream, &pe->line, &pe->linelen) == -1)
+               return -1;
+
+       p = pe->line;
+       switch (p[0]) {
+       case '+':
+               dd(printf("Got + response\n"));
+               if (pc->flags & CAMEL_POP3_COMMAND_MULTI) {
+                       pc->state = CAMEL_POP3_COMMAND_DATA;
+                       camel_pop3_stream_set_mode(pe->stream, CAMEL_POP3_STREAM_DATA);
+
+                       if (pc->func)
+                               pc->func(pe, pe->stream, pc->func_data);
+
+                       /* Make sure we get all data before going back to command mode */
+                       while (camel_pop3_stream_getd(pe->stream, &p, &len) > 0)
+                               ;
+                       camel_pop3_stream_set_mode(pe->stream, CAMEL_POP3_STREAM_LINE);
+               } else {
+                       pc->state = CAMEL_POP3_COMMAND_OK;
+               }
+               break;
+       case '-':
+               pc->state = CAMEL_POP3_COMMAND_ERR;
+               break;
+       default:
+               /* what do we do now?  f'knows! */
+               g_warning("Bad server response: %s\n", p);
+               errno = EIO;
+               return -1;
+       }
+
+       e_dlist_addtail(&pe->done, (EDListNode *)pc);
+       pe->sentlen -= strlen(pc->data);
+
+       /* Set next command */
+       pe->current = (CamelPOP3Command *)e_dlist_remhead(&pe->active);
+
+       /* check the queue for sending any we can now send also */
+       pw = (CamelPOP3Command *)pe->queue.head;
+       pn = pw->next;
+       while (pn) {
+               if (((pe->capa & CAMEL_POP3_CAP_PIPE) == 0 || (pe->sentlen + strlen(pw->data)) > CAMEL_POP3_SEND_LIMIT)
+                   && pe->current != NULL)
+                       break;
+
+               if (camel_stream_write((CamelStream *)pe->stream, pw->data, strlen(pw->data)) == -1)
+                       return -1;
+
+               e_dlist_remove((EDListNode *)pw);
+
+
+               pe->sentlen += strlen(pw->data);
+               pw->state = CAMEL_POP3_COMMAND_DISPATCHED;
+
+               if (pe->current == NULL)
+                       pe->current = pw;
+               else
+                       e_dlist_addtail(&pe->active, (EDListNode *)pw);
+
+               pw = pn;
+               pn = pn->next;
+       }
+
+       /* UNLOCK */
+
+       if (pcwait && pcwait->state >= CAMEL_POP3_COMMAND_OK)
+               return 0;
+
+       return pe->current==NULL?0:1;
+}
+
+CamelPOP3Command *
+camel_pop3_engine_command_new(CamelPOP3Engine *pe, guint32 flags, CamelPOP3CommandFunc func, void *data, const char *fmt, ...)
+{
+       CamelPOP3Command *pc;
+       va_list ap;
+
+       pc = g_malloc0(sizeof(*pc));
+       pc->func = func;
+       pc->func_data = data;
+       pc->flags = flags;
+       
+       va_start(ap, fmt);
+       pc->data = g_strdup_vprintf(fmt, ap);
+       pc->state = CAMEL_POP3_COMMAND_IDLE;
+
+       /* TODO: what abou write errors? */
+       engine_command_queue(pe, pc);
+
+       return pc;
+}
+
+void
+camel_pop3_engine_command_free(CamelPOP3Engine *pe, CamelPOP3Command *pc)
+{
+       if (pe->current != pc)
+               e_dlist_remove((EDListNode *)pc);
+       g_free(pc->data);
+       g_free(pc);
+}
diff --git a/camel/providers/pop3/camel-pop3-engine.h b/camel/providers/pop3/camel-pop3-engine.h
new file mode 100644 (file)
index 0000000..418bc5e
--- /dev/null
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2001 Ximian Inc.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef _CAMEL_POP3_ENGINE_H
+#define _CAMEL_POP3_ENGINE_H
+
+#include <camel/camel-object.h>
+#include "e-util/e-msgport.h"
+#include "camel-pop3-stream.h"
+
+#define CAMEL_POP3_ENGINE(obj)         CAMEL_CHECK_CAST (obj, camel_pop3_engine_get_type (), CamelPOP3Engine)
+#define CAMEL_POP3_ENGINE_CLASS(klass) CAMEL_CHECK_CLASS_CAST (klass, camel_pop3_engine_get_type (), CamelPOP3EngineClass)
+#define CAMEL_IS_POP3_ENGINE(obj)      CAMEL_CHECK_TYPE (obj, camel_pop3_engine_get_type ())
+
+typedef struct _CamelPOP3EngineClass CamelPOP3EngineClass;
+typedef struct _CamelPOP3Engine CamelPOP3Engine;
+typedef struct _CamelPOP3Command CamelPOP3Command;
+
+/* pop 3 connection states, actually since we're given a connected socket, we always start in auth state */
+typedef enum {
+       CAMEL_POP3_ENGINE_DISCONNECT = 0,
+       CAMEL_POP3_ENGINE_AUTH,
+       CAMEL_POP3_ENGINE_TRANSACTION,
+       CAMEL_POP3_ENGINE_UPDATE,
+} camel_pop3_engine_t;
+
+/* state of a command */
+typedef enum {
+       CAMEL_POP3_COMMAND_IDLE = 0, /* command created or queued, not yet sent (e.g. non pipelined server) */
+       CAMEL_POP3_COMMAND_DISPATCHED, /* command sent to server */
+
+       /* completion codes */
+       CAMEL_POP3_COMMAND_OK,  /* plain ok response */
+       CAMEL_POP3_COMMAND_DATA, /* processing command response */
+       CAMEL_POP3_COMMAND_ERR, /* error response */
+} camel_pop3_command_t;
+
+/* flags for command types */
+enum {
+       CAMEL_POP3_COMMAND_SIMPLE = 0, /* dont expect multiline response */
+       CAMEL_POP3_COMMAND_MULTI = 1, /* expect multiline response */
+};
+
+/* flags for server options */
+enum {
+       CAMEL_POP3_CAP_APOP = 1<<0,
+       CAMEL_POP3_CAP_UIDL = 1<<1,
+       CAMEL_POP3_CAP_SASL = 1<<2,
+       CAMEL_POP3_CAP_TOP  = 1<<3,
+       CAMEL_POP3_CAP_PIPE = 1<<4,
+};
+
+typedef void (*CamelPOP3CommandFunc)(CamelPOP3Engine *pe, CamelPOP3Stream *stream, void *data);
+
+struct _CamelPOP3Command {
+       struct _CamelPOP3Command *next;
+       struct _CamelPOP3Command *prev;
+
+       guint32 flags;
+       camel_pop3_command_t state;
+
+       CamelPOP3CommandFunc func;
+       void *func_data;
+
+       int data_size;
+       char *data;
+};
+
+struct _CamelPOP3Engine {
+       CamelObject parent;
+
+       camel_pop3_engine_t state;
+
+       GList *auth;            /* authtypes supported */
+
+       guint32 capa;           /* capabilities */
+       char *apop;             /* apop time string */
+
+       unsigned char *line;    /* current line buffer */
+       unsigned int linelen;
+
+       struct _CamelPOP3Stream *stream;
+
+       unsigned int sentlen;   /* data sent (so we dont overflow network buffer) */
+
+       EDList active;          /* active commands */
+       EDList queue;           /* queue of waiting commands */
+       EDList done;            /* list of done commands, awaiting free */
+
+       CamelPOP3Command *current; /* currently busy (downloading) response */
+};
+
+struct _CamelPOP3EngineClass {
+       CamelObjectClass parent_class;
+};
+
+guint            camel_pop3_engine_get_type    (void);
+
+CamelPOP3Engine  *camel_pop3_engine_new                (CamelStream *source);
+void              camel_pop3_engine_command_free(CamelPOP3Engine *pe, CamelPOP3Command *pc);
+
+int              camel_pop3_engine_iterate     (CamelPOP3Engine *pe, CamelPOP3Command *pc);
+
+CamelPOP3Command *camel_pop3_engine_command_new        (CamelPOP3Engine *pe, guint32 flags, CamelPOP3CommandFunc func, void *data, const char *fmt, ...);
+
+#endif /* ! _CAMEL_POP3_ENGINE_H */
index 5452144..777f81f 100644 (file)
@@ -4,8 +4,9 @@
 /* 
  * Authors:
  *   Dan Winship <danw@ximian.com>
+ *   Michael Zucchi <notzed@ximian.com>
  *
- * Copyright (C) 2000 Ximian, Inc. (www.ximian.com)
+ * Copyright (C) 2002 Ximian, Inc. (www.ximian.com)
  *
  * This program is free software; you can redistribute it and/or 
  * modify it under the terms of version 2 of the GNU General Public 
@@ -26,6 +27,8 @@
 #include <config.h>
 #endif
 
+#include <errno.h>
+
 #include "camel-pop3-folder.h"
 #include "camel-pop3-store.h"
 #include "camel-exception.h"
 #include "camel-stream-filter.h"
 #include "camel-mime-message.h"
 #include "camel-operation.h"
+#include "camel-data-cache.h"
 
 #include <e-util/md5-utils.h>
 
 #include <stdlib.h>
 #include <string.h>
 
+#define d(x)
+
 #define CF_CLASS(o) (CAMEL_FOLDER_CLASS (CAMEL_OBJECT_GET_CLASS(o)))
 static CamelFolderClass *parent_class;
 
 static void pop3_finalize (CamelObject *object);
-
 static void pop3_refresh_info (CamelFolder *folder, CamelException *ex);
-static void pop3_sync (CamelFolder *folder, gboolean expunge,
-                      CamelException *ex);
-
+static void pop3_sync (CamelFolder *folder, gboolean expunge, CamelException *ex);
 static gint pop3_get_message_count (CamelFolder *folder);
 static GPtrArray *pop3_get_uids (CamelFolder *folder);
-static CamelStreamMem *pop3_get_message_stream (CamelFolder *folder, int id,
-                                               gboolean headers_only, CamelException *ex);
-static CamelMimeMessage *pop3_get_message (CamelFolder *folder, 
-                                          const char *uid,
-                                          CamelException *ex);
-static void pop3_set_message_flags (CamelFolder *folder, const char *uid,
-                                   guint32 flags, guint32 set);
-
-static GPtrArray *parse_listing (int count, char *data);
+static CamelMimeMessage *pop3_get_message (CamelFolder *folder, const char *uid, CamelException *ex);
+static void pop3_set_message_flags (CamelFolder *folder, const char *uid, guint32 flags, guint32 set);
 
 static void
-camel_pop3_folder_class_init (CamelPop3FolderClass *camel_pop3_folder_class)
+camel_pop3_folder_class_init (CamelPOP3FolderClass *camel_pop3_folder_class)
 {
        CamelFolderClass *camel_folder_class =
                CAMEL_FOLDER_CLASS (camel_pop3_folder_class);
@@ -74,7 +70,7 @@ camel_pop3_folder_class_init (CamelPop3FolderClass *camel_pop3_folder_class)
        
        camel_folder_class->get_message_count = pop3_get_message_count;
        camel_folder_class->get_uids = pop3_get_uids;
-       camel_folder_class->free_uids = camel_folder_free_nop;
+       camel_folder_class->free_uids = camel_folder_free_shallow;
        
        camel_folder_class->get_message = pop3_get_message;
        camel_folder_class->set_message_flags = pop3_set_message_flags;
@@ -86,9 +82,9 @@ camel_pop3_folder_get_type (void)
        static CamelType camel_pop3_folder_type = CAMEL_INVALID_TYPE;
        
        if (!camel_pop3_folder_type) {
-               camel_pop3_folder_type = camel_type_register (CAMEL_FOLDER_TYPE, "CamelPop3Folder",
-                                                             sizeof (CamelPop3Folder),
-                                                             sizeof (CamelPop3FolderClass),
+               camel_pop3_folder_type = camel_type_register (CAMEL_FOLDER_TYPE, "CamelPOP3Folder",
+                                                             sizeof (CamelPOP3Folder),
+                                                             sizeof (CamelPOP3FolderClass),
                                                              (CamelObjectClassInitFunc) camel_pop3_folder_class_init,
                                                              NULL,
                                                              NULL,
@@ -101,18 +97,24 @@ camel_pop3_folder_get_type (void)
 void
 pop3_finalize (CamelObject *object)
 {
-       CamelPop3Folder *pop3_folder = CAMEL_POP3_FOLDER (object);
-       
-       if (pop3_folder->uids)
-               camel_folder_free_deep (NULL, pop3_folder->uids);
-       if (pop3_folder->flags)
-               g_free (pop3_folder->flags);
+       CamelPOP3Folder *pop3_folder = CAMEL_POP3_FOLDER (object);
+       CamelPOP3FolderInfo **fi = (CamelPOP3FolderInfo **)pop3_folder->uids->pdata;
+       int i;
+
+       for (i=0;i<pop3_folder->uids->len;i++,fi++) {
+               g_free(fi[0]->uid);
+               g_free(fi[0]);
+       }
+
+       g_ptr_array_free(pop3_folder->uids, TRUE);
 }
 
 CamelFolder *
 camel_pop3_folder_new (CamelStore *parent, CamelException *ex)
 {
        CamelFolder *folder;
+
+       d(printf("opening pop3 INBOX folder\n"));
        
        folder = CAMEL_FOLDER (camel_object_new (CAMEL_POP3_FOLDER_TYPE));
        camel_folder_construct (folder, parent, "inbox", "inbox");
@@ -127,281 +129,413 @@ camel_pop3_folder_new (CamelStore *parent, CamelException *ex)
        return folder;
 }
 
-static GPtrArray *
-pop3_generate_uids (CamelFolder *folder, int count, CamelException *ex)
+static CamelPOP3FolderInfo *
+id_to_fi(CamelPOP3Folder *folder, guint32 id)
 {
-       GPtrArray *uids;
        int i;
-       
-       uids = g_ptr_array_new ();
-       g_ptr_array_set_size (uids, count);
-       
-       for (i = 0; i < count; i++) {
-               CamelStreamMem *stream;
-               guchar digest[16];
-               char *uid;
-               
-               stream = pop3_get_message_stream (folder, i + 1, TRUE, ex);
-               if (stream == NULL)
-                       goto exception;
-               
-               md5_get_digest (stream->buffer->data, stream->buffer->len, digest);
-               camel_object_unref (CAMEL_OBJECT (stream));
-               
-               uid = base64_encode_simple (digest, 16);
-               uids->pdata[i] = uid;
-       }
-       
-       return uids;
-       
- exception:
-       
-       for (i = 0; i < count; i++)
-               g_free (uids->pdata[i]);
-       g_ptr_array_free (uids, TRUE);
-       
+       CamelPOP3FolderInfo **fi = (CamelPOP3FolderInfo **)folder->uids->pdata;
+       int len = folder->uids->len;
+
+       for (i=0;i<len;i++, fi++)
+               if (fi[0]->id == id)
+                       return fi[0];
+
+       return NULL;
+}
+
+static CamelPOP3FolderInfo *
+uid_to_fi(CamelPOP3Folder *folder, const char *uid)
+{
+       int i;
+       CamelPOP3FolderInfo **fi = (CamelPOP3FolderInfo **)folder->uids->pdata;
+       int len = folder->uids->len;
+
+       for (i=0;i<len;i++,fi++)
+               if (fi[0]->uid && strcmp(fi[0]->uid, uid) == 0)
+                       return fi[0];
+
        return NULL;
 }
 
+static int
+fi_to_index(CamelPOP3Folder *folder, CamelPOP3FolderInfo *fin)
+{
+       int i;
+       CamelPOP3FolderInfo **fi = (CamelPOP3FolderInfo **)folder->uids->pdata;
+       int len = folder->uids->len;
+
+       for (i=0;i<len;i++,fi++)
+               if (fi[0] == fin)
+                       return i;
+
+       return -1;
+}
+
+/* create a uid from md5 of 'top' output */
+static void
+cmd_builduid(CamelPOP3Engine *pe, CamelPOP3Stream *stream, void *data)
+{
+       CamelPOP3FolderInfo *fi = data;
+       MD5Context md5;
+       unsigned char *start;
+       unsigned int len;
+       unsigned char digest[16];
+       int ret;
+
+       /* TODO; somehow work out the limit and use that for proper progress reporting
+          We need a pointer to the folder perhaps? */
+       camel_operation_progress_count(NULL, fi->id);
+
+       md5_init(&md5);
+       do {
+               ret = camel_pop3_stream_getd(stream, &start, &len);
+               if (ret >= 0)
+                       md5_update(&md5, start, len);
+       } while (ret > 0);
+       md5_final(&md5, digest);
+       fi->uid = base64_encode_simple (digest, 16);
+
+       d(printf("building uid for id '%d' = '%s'\n", fi->id, fi->uid));
+}
+
+static void
+cmd_list(CamelPOP3Engine *pe, CamelPOP3Stream *stream, void *data)
+{
+       int ret;
+       unsigned int len, id, size;
+       unsigned char *line;
+       CamelFolder *folder = data;
+       CamelPOP3Store *pop3_store = CAMEL_POP3_STORE (folder->parent_store);
+       CamelPOP3FolderInfo *fi;
+
+       do {
+               ret = camel_pop3_stream_line(stream, &line, &len);
+               if (ret>=0) {
+                       if (sscanf(line, "%u %u", &id, &size) == 2) {
+                               fi = g_malloc0(sizeof(*fi));
+                               fi->size = size;
+                               fi->id = id;
+                               if ((pop3_store->engine->capa & CAMEL_POP3_CAP_UIDL) == 0)
+                                       fi->cmd = camel_pop3_engine_command_new(pe, CAMEL_POP3_COMMAND_MULTI, cmd_builduid, fi, "TOP %u 0\r\n", id);
+                               g_ptr_array_add(((CamelPOP3Folder *)folder)->uids, fi);
+                       }
+               }
+       } while (ret>0);
+}
+
+static void
+cmd_uidl(CamelPOP3Engine *pe, CamelPOP3Stream *stream, void *data)
+{
+       int ret;
+       unsigned int len;
+       unsigned char *line;
+       char uid[1025];
+       unsigned int id, i=0;
+       CamelPOP3FolderInfo *fi;
+       CamelPOP3Folder *folder = data;
+       
+       do {
+               ret = camel_pop3_stream_line(stream, &line, &len);
+               if (ret>=0) {                   
+                       if (strlen(line) > 1024)
+                               line[1024] = 0;
+                       if (sscanf(line, "%u %s", &id, uid) == 2) {
+                               fi = id_to_fi(folder, id);
+                               if (fi) {
+                                       /* fixme: dreadfully inefficient */
+                                       i = fi_to_index(folder, fi);
+                                       camel_operation_progress(NULL, (i+1) * 100 / folder->uids->len);
+                                       fi->uid = g_strdup(uid);
+                               } else {
+                                       g_warning("ID %u (uid: %s) not in previous LIST output", id, uid);
+                               }
+                       }
+               }
+       } while (ret>0);
+}
+
 static void 
 pop3_refresh_info (CamelFolder *folder, CamelException *ex)
 {
-       CamelPop3Store *pop3_store = CAMEL_POP3_STORE (folder->parent_store);
-       CamelPop3Folder *pop3_folder = (CamelPop3Folder *) folder;
-       GPtrArray *uids;
-       int status, count;
-       char *data;
-       
+       CamelPOP3Store *pop3_store = CAMEL_POP3_STORE (folder->parent_store);
+       CamelPOP3Folder *pop3_folder = (CamelPOP3Folder *) folder;
+       CamelPOP3Command *pcl, *pcu = NULL;
+       int i;
+
        camel_operation_start (NULL, _("Retrieving POP summary"));
-       
-       status = camel_pop3_command (pop3_store, &data, ex, "STAT");
-       switch (status) {
-       case CAMEL_POP3_ERR:
-               camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
-                                     _("Could not check POP server for new messages: %s"),
-                                     data);
-               g_free (data);
-               /* fall through */
-       case CAMEL_POP3_FAIL:
-               camel_operation_end (NULL);
-               return;
-       }
-       
-       count = atoi (data);
-       g_free (data);
-       
-       if (count == 0) {
-               camel_operation_end (NULL);
-               pop3_folder->uids = g_ptr_array_new ();
-               pop3_folder->flags = g_new0 (guint32, 0);
-               return;
+
+       pop3_folder->uids = g_ptr_array_new ();
+
+       pcl = camel_pop3_engine_command_new(pop3_store->engine, CAMEL_POP3_COMMAND_MULTI, cmd_list, folder, "LIST\r\n");
+       if (pop3_store->engine->capa & CAMEL_POP3_CAP_UIDL) {
+               pcu = camel_pop3_engine_command_new(pop3_store->engine, CAMEL_POP3_COMMAND_MULTI, cmd_uidl, folder, "UIDL\r\n");
        }
+       while ((i = camel_pop3_engine_iterate(pop3_store->engine, NULL)) > 0)
+               ;
 
-       if (pop3_store->supports_uidl != FALSE) {
-               status = camel_pop3_command (pop3_store, NULL, ex, "UIDL");
-               switch (status) {
-               case CAMEL_POP3_ERR:
-                       pop3_store->supports_uidl = FALSE;
-                       break;
-               case CAMEL_POP3_FAIL:
-                       camel_operation_end (NULL);
-                       return;
-               }
+       if (i == -1) {
+               if (errno == EINTR)
+                       camel_exception_setv(ex, CAMEL_EXCEPTION_USER_CANCEL, _("User cancelled"));
+               else
+                       camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM, _("Cannot get POP summary: %s"), strerror(errno));
        }
-       
-       if (pop3_store->supports_uidl == FALSE) {
-               uids = pop3_generate_uids (folder, count, ex);
-               camel_operation_end (NULL);
-               if (!uids || camel_exception_is_set (ex))
-                       return;
+
+       /* TODO: check every id has a uid & commands returned OK too? */
+
+       /* Free any commands we created along the way */
+       if (pop3_store->engine->capa & CAMEL_POP3_CAP_UIDL) {
+               camel_pop3_engine_command_free(pop3_store->engine, pcu);
        } else {
-               data = camel_pop3_command_get_additional_data (pop3_store, 0, ex);
-               camel_operation_end (NULL);
-               if (!data || camel_exception_is_set (ex))
-                       return;
-               
-               uids = parse_listing (count, data);
-               g_free (data);
-               
-               if (!uids) {
-                       camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
-                                             _("Could not open folder: "
-                                               "message listing was "
-                                               "incomplete."));
-                       return;
+               for (i=0;i<pop3_folder->uids->len;i++) {
+                       CamelPOP3FolderInfo *fi = pop3_folder->uids->pdata[i];
+                       if (fi->cmd) {
+                               camel_pop3_engine_command_free(pop3_store->engine, fi->cmd);
+                               fi->cmd = NULL;
+                       }
                }
        }
-       
-       pop3_folder->uids = uids;
-       pop3_folder->flags = g_new0 (guint32, uids->len);
+
+       camel_operation_end (NULL);
+       return;
 }
 
 static void
 pop3_sync (CamelFolder *folder, gboolean expunge, CamelException *ex)
 {
-       CamelPop3Folder *pop3_folder;
-       CamelPop3Store *pop3_store;
-       int i, status;
-       
+       CamelPOP3Folder *pop3_folder;
+       CamelPOP3Store *pop3_store;
+       int i;
+       CamelPOP3FolderInfo *fi;
+
        if (!expunge)
                return;
-       
+
        pop3_folder = CAMEL_POP3_FOLDER (folder);
        pop3_store = CAMEL_POP3_STORE (folder->parent_store);
 
        camel_operation_start(NULL, _("Expunging deleted messages"));
        
        for (i = 0; i < pop3_folder->uids->len; i++) {
-               camel_operation_progress(NULL, (i+1) * 100 / pop3_folder->uids->len);
-               if (pop3_folder->flags[i] & CAMEL_MESSAGE_DELETED) {
-                       status = camel_pop3_command (pop3_store, NULL, ex,
-                                                    "DELE %d", i + 1);
-                       if (status != CAMEL_POP3_OK) {
-                               camel_operation_end(NULL);
-                               return;
-                       }
+               fi = pop3_folder->uids->pdata[i];
+               /* busy already?  wait for that to finish first */
+               if (fi->cmd) {
+                       while (camel_pop3_engine_iterate(pop3_store->engine, fi->cmd) > 0)
+                               ;
+                       camel_pop3_engine_command_free(pop3_store->engine, fi->cmd);
+                       fi->cmd = NULL;
+               }
+
+               if (fi->flags & CAMEL_MESSAGE_DELETED) {
+                       fi->cmd = camel_pop3_engine_command_new(pop3_store->engine, 0, NULL, NULL, "DELE %u\r\n", fi->id);
+
+                       /* also remove from cache */
+                       if (pop3_store->cache && fi->uid)
+                               camel_data_cache_remove(pop3_store->cache, "cache", fi->uid, NULL);
                }
        }
 
+       for (i = 0; i < pop3_folder->uids->len; i++) {
+               fi = pop3_folder->uids->pdata[i];
+               /* wait for delete commands to finish */
+               if (fi->cmd) {
+                       while (camel_pop3_engine_iterate(pop3_store->engine, fi->cmd) > 0)
+                               ;
+                       camel_pop3_engine_command_free(pop3_store->engine, fi->cmd);
+                       fi->cmd = NULL;
+               }
+               camel_operation_progress(NULL, (i+1) * 100 / pop3_folder->uids->len);
+       }
+
        camel_operation_end(NULL);
 
        camel_pop3_store_expunge (pop3_store, ex);
 }
 
-
-static GPtrArray *
-parse_listing (int count, char *data)
+static void
+cmd_tocache(CamelPOP3Engine *pe, CamelPOP3Stream *stream, void *data)
 {
-       GPtrArray *ans;
-       char *p;
-       int index, len;
-       
-       ans = g_ptr_array_new ();
-       g_ptr_array_set_size (ans, count);
-       
-       p = data;
-       while (*p) {
-               index = strtoul (p, &p, 10);
-               len = strcspn (p, "\n");
-               if (index <= count && *p == ' ')
-                       ans->pdata[index - 1] = g_strndup (p + 1, len - 1);
-               p += len;
-               if (*p == '\n')
-                       p++;
-       }
-       
-       for (index = 0; index < count; index++) {
-               if (ans->pdata[index] == NULL) {
-                       g_ptr_array_free (ans, TRUE);
-                       return NULL;
-               }
-       }
-       
-       return ans;
-}
+       CamelPOP3FolderInfo *fi = data;
+       char buffer[2048];
+       int w = 0, n;
 
-static int
-uid_to_number (CamelPop3Folder *pop3_folder, const char *uid)
-{
-       int i;
-       
-       for (i = 0; i < pop3_folder->uids->len; i++) {
-               if (!strcmp (uid, pop3_folder->uids->pdata[i]))
-                       return i + 1;
+       /* What if it fails? */
+
+       /* We write an '*' to the start of the stream to say its not complete yet */
+       /* This should probably be part of the cache code */
+       if ((n = camel_stream_write(fi->stream, "*", 1)) == -1)
+               goto done;
+
+       while ((n = camel_stream_read((CamelStream *)stream, buffer, sizeof(buffer))) > 0) {
+               n = camel_stream_write(fi->stream, buffer, n);
+               if (n == -1)
+                       break;
+
+               w += n;
+               if (w > fi->size)
+                       w = fi->size;
+               camel_operation_progress(NULL, (w * 100) / fi->size);
        }
-       
-       return -1;
-}
 
-static CamelStreamMem *
-pop3_get_message_stream (CamelFolder *folder, int id, gboolean headers_only, CamelException *ex)
-{
-       CamelStream *stream;
-       char *result, *body;
-       int status, total;
-       
-       status = camel_pop3_command (CAMEL_POP3_STORE (folder->parent_store),
-                                    &result, ex, headers_only ? "TOP %d 0" : "RETR %d", id);
-       switch (status) {
-       case CAMEL_POP3_ERR:
-               camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
-                                     _("Could not fetch message: %s"), result);
-               g_free (result);
-               /* fall through */
-       case CAMEL_POP3_FAIL:
-               camel_operation_end (NULL);
-               return NULL;
+       /* it all worked, output a '#' to say we're a-ok */
+       if (n != -1) {
+               camel_stream_reset(fi->stream);
+               n = camel_stream_write(fi->stream, "#", 1);
        }
-       
-       if (!result || (result && sscanf (result, "%d", &total) != 1))
-               total = 0;
-       
-       g_free (result);
-       body = camel_pop3_command_get_additional_data (CAMEL_POP3_STORE (folder->parent_store), total, ex);
-       if (!body) {
-               CamelService *service = CAMEL_SERVICE (folder->parent_store);
-               camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
-                                     _("Could not retrieve message from POP "
-                                       "server %s: %s"), service->url->host,
-                                     camel_exception_get_description (ex));
-               camel_operation_end (NULL);
-               return NULL;
+done:
+       if (n == -1) {
+               fi->err = errno;
+               g_warning("POP3 retrieval failed: %s", strerror(errno));
+       } else {
+               fi->err = 0;
        }
        
-       stream = camel_stream_mem_new_with_buffer (body, strlen (body));
-       g_free (body);
-       
-       return CAMEL_STREAM_MEM (stream);
+       camel_object_unref((CamelObject *)fi->stream);
+       fi->stream = NULL;
 }
 
 static CamelMimeMessage *
 pop3_get_message (CamelFolder *folder, const char *uid, CamelException *ex)
 {
-       CamelMimeMessage *message;
-       CamelStreamMem *stream;
-       int id;
-       
-       id = uid_to_number (CAMEL_POP3_FOLDER (folder), uid);
-       if (id == -1) {
+       CamelMimeMessage *message = NULL;
+       CamelPOP3Store *pop3_store = CAMEL_POP3_STORE (folder->parent_store);
+       CamelPOP3Folder *pop3_folder = (CamelPOP3Folder *)folder;
+       CamelPOP3Command *pcr;
+       CamelPOP3FolderInfo *fi;
+       char buffer[1];
+       int ok, i;
+       CamelStream *stream = NULL;
+
+       fi = uid_to_fi(pop3_folder, uid);
+       if (fi == NULL) {
                camel_exception_setv (ex, CAMEL_EXCEPTION_FOLDER_INVALID_UID,
                                      _("No message with uid %s"), uid);
                return NULL;
        }
+
+       /* Sigh, most of the crap in this function is so that the cancel button
+          returns the proper exception code.  Sigh. */
+
+       camel_operation_start_transient(NULL, _("Retrieving POP message %d"), fi->id);
+
+       /* If we have an oustanding retrieve message running, wait for that to complete
+          & then retrieve from cache, otherwise, start a new one, and similar */
+
+       if (fi->cmd != NULL) {
+               while ((i = camel_pop3_engine_iterate(pop3_store->engine, fi->cmd)) > 0)
+                       ;
+
+               if (i == -1)
+                       fi->err = errno;
+
+               /* getting error code? */
+               ok = fi->cmd->state == CAMEL_POP3_COMMAND_DATA;
+               camel_pop3_engine_command_free(pop3_store->engine, fi->cmd);
+               fi->cmd = NULL;
+
+               if (fi->err != 0) {
+                       if (fi->err == EINTR)
+                               camel_exception_setv(ex, CAMEL_EXCEPTION_USER_CANCEL, _("User cancelled"));
+                       else
+                               camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM, _("Cannot get message %s: %s"), uid, strerror(fi->err));
+                       goto fail;
+               }
+       }
        
-       camel_operation_start_transient (NULL, _("Retrieving POP message %d"), id);
-       stream = pop3_get_message_stream (folder, id, FALSE, ex);
-       camel_operation_end (NULL);
-       if (stream == NULL)
-               return NULL;
-       
+       /* check to see if we have safely written flag set */
+       if (pop3_store->cache == NULL
+           || (stream = camel_data_cache_get(pop3_store->cache, "cache", fi->uid, NULL)) == NULL
+           || camel_stream_read(stream, buffer, 1) != 1
+           || buffer[0] != '#') {
+
+               /* Initiate retrieval, if disk backing fails, use a memory backing */
+               if (pop3_store->cache == NULL
+                   || (stream = camel_data_cache_add(pop3_store->cache, "cache", fi->uid, NULL)) == NULL)
+                       stream = camel_stream_mem_new();
+
+               /* ref it, the cache storage routine unref's when done */
+               camel_object_ref((CamelObject *)stream);
+               fi->stream = stream;
+               fi->err = EIO;
+               pcr = camel_pop3_engine_command_new(pop3_store->engine, CAMEL_POP3_COMMAND_MULTI, cmd_tocache, fi, "RETR %u\r\n", fi->id);
+
+               /* Also initiate retrieval of all following messages, assume we'll be receiving them */
+               if (pop3_store->cache != NULL) {
+                       i = fi_to_index(pop3_folder, fi)+1;
+                       for (;i<pop3_folder->uids->len;i++) {
+                               CamelPOP3FolderInfo *pfi = pop3_folder->uids->pdata[i];
+                               
+                               if (pfi->uid && pfi->cmd == NULL) {
+                                       pfi->stream = camel_data_cache_add(pop3_store->cache, "cache", pfi->uid, NULL);
+                                       if (pfi->stream) {
+                                               pfi->err = EIO;
+                                               pfi->cmd = camel_pop3_engine_command_new(pop3_store->engine, CAMEL_POP3_COMMAND_MULTI,
+                                                                                        cmd_tocache, pfi, "RETR %u\r\n", pfi->id);
+                                       }
+                               }
+                       }
+               }
+
+               /* now wait for the first one to finish */
+               while ((i = camel_pop3_engine_iterate(pop3_store->engine, pcr)) > 0)
+                       ;
+
+               if (i == -1)
+                       fi->err = errno;
+
+               /* getting error code? */
+               ok = pcr->state == CAMEL_POP3_COMMAND_DATA;
+               camel_pop3_engine_command_free(pop3_store->engine, pcr);
+               camel_stream_reset(stream);
+
+               /* Check to see we have safely written flag set */
+               if (fi->err != 0) {
+                       if (fi->err == EINTR)
+                               camel_exception_setv(ex, CAMEL_EXCEPTION_USER_CANCEL, _("User cancelled"));
+                       else
+                               camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM, _("Cannot get message %s: %s"), uid, strerror(fi->err));
+                       goto done;
+               }
+
+               if (camel_stream_read(stream, buffer, 1) != 1
+                   || buffer[0] != '#') {
+                       camel_exception_setv(ex, CAMEL_EXCEPTION_FOLDER_INVALID_UID,
+                                            _("Cannot get message %s: %s"), uid, _("Unknown reason"));
+                       goto done;
+               }
+       }
+
        message = camel_mime_message_new ();
-       camel_data_wrapper_construct_from_stream (CAMEL_DATA_WRAPPER (message),
-                                                 CAMEL_STREAM (stream));
-       
-       camel_object_unref (CAMEL_OBJECT (stream));
-       
+       if (camel_data_wrapper_construct_from_stream((CamelDataWrapper *)message, stream) == -1) {
+               if (errno == EINTR)
+                       camel_exception_setv(ex, CAMEL_EXCEPTION_USER_CANCEL, _("User cancelled"));
+               else
+                       camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM, _("Cannot get message %s: %s"), uid, strerror(errno));
+               camel_object_unref((CamelObject *)message);
+               message = NULL;
+       }
+done:
+       camel_object_unref((CamelObject *)stream);
+fail:
+       camel_operation_end(NULL);
+
        return message;
 }
 
 static void
-pop3_set_message_flags (CamelFolder *folder, const char *uid,
-                       guint32 flags, guint32 set)
+pop3_set_message_flags (CamelFolder *folder, const char *uid, guint32 flags, guint32 set)
 {
-       CamelPop3Folder *pop3_folder = CAMEL_POP3_FOLDER (folder);
-       int num;
-       
-       num = uid_to_number (pop3_folder, uid);
-       if (num == -1)
-               return;
-       
-       pop3_folder->flags[num - 1] =
-               (pop3_folder->flags[num] & ~flags) | (set & flags);
+       CamelPOP3Folder *pop3_folder = CAMEL_POP3_FOLDER (folder);
+       CamelPOP3FolderInfo *fi;
+
+       fi = uid_to_fi(pop3_folder, uid);
+       if (fi)
+               fi->flags = (fi->flags & ~flags) | (set & flags);
 }
 
 static gint
 pop3_get_message_count (CamelFolder *folder)
 {
-       CamelPop3Folder *pop3_folder = CAMEL_POP3_FOLDER (folder);
+       CamelPOP3Folder *pop3_folder = CAMEL_POP3_FOLDER (folder);
        
        return pop3_folder->uids->len;
 }
@@ -409,7 +543,15 @@ pop3_get_message_count (CamelFolder *folder)
 static GPtrArray *
 pop3_get_uids (CamelFolder *folder)
 {
-       CamelPop3Folder *pop3_folder = CAMEL_POP3_FOLDER (folder);
+       CamelPOP3Folder *pop3_folder = CAMEL_POP3_FOLDER (folder);
+       GPtrArray *uids = g_ptr_array_new();
+       CamelPOP3FolderInfo **fi = (CamelPOP3FolderInfo **)pop3_folder->uids->pdata;
+       int i;
+
+       for (i=0;i<pop3_folder->uids->len;i++,fi++) {
+               if (fi[0]->uid)
+                       g_ptr_array_add(uids, fi[0]->uid);
+       }
        
-       return pop3_folder->uids;
+       return uids;
 }
index 0b6296a..55eb1d2 100644 (file)
@@ -2,10 +2,11 @@
 /* camel-pop3-folder.h : Class for a POP3 folder */
 
 /* 
- * Author:
+ * Authors:
  *   Dan Winship <danw@ximian.com>
+ *   Michael Zucchi <notzed@ximian.com>
  *
- * Copyright (C) 2000 Ximian, Inc. (www.ximian.com)
+ * Copyright (C) 2002 Ximian, Inc. (www.ximian.com)
  *
  * This program is free software; you can redistribute it and/or 
  * modify it under the terms of version 2 of the GNU General Public 
@@ -35,18 +36,27 @@ extern "C" {
 #include "camel-folder.h"
 
 #define CAMEL_POP3_FOLDER_TYPE     (camel_pop3_folder_get_type ())
-#define CAMEL_POP3_FOLDER(obj)     (CAMEL_CHECK_CAST((obj), CAMEL_POP3_FOLDER_TYPE, CamelPop3Folder))
-#define CAMEL_POP3_FOLDER_CLASS(k) (CAMEL_CHECK_CLASS_CAST ((k), CAMEL_POP3_FOLDER_TYPE, CamelPop3FolderClass))
+#define CAMEL_POP3_FOLDER(obj)     (CAMEL_CHECK_CAST((obj), CAMEL_POP3_FOLDER_TYPE, CamelPOP3Folder))
+#define CAMEL_POP3_FOLDER_CLASS(k) (CAMEL_CHECK_CLASS_CAST ((k), CAMEL_POP3_FOLDER_TYPE, CamelPOP3FolderClass))
 #define CAMEL_IS_POP3_FOLDER(o)    (CAMEL_CHECK_TYPE((o), CAMEL_POP3_FOLDER_TYPE))
 
+typedef struct {
+       guint32 id;
+       guint32 size;
+       guint32 flags;
+       char *uid;
+       int err;
+       struct _CamelPOP3Command *cmd;
+       struct _CamelStream *stream;
+} CamelPOP3FolderInfo;
+
 
 typedef struct {
        CamelFolder parent_object;
 
        GPtrArray *uids;
-       guint32 *flags;
 
-} CamelPop3Folder;
+} CamelPOP3Folder;
 
 
 
@@ -55,7 +65,7 @@ typedef struct {
 
        /* Virtual methods */   
        
-} CamelPop3FolderClass;
+} CamelPOP3FolderClass;
 
 
 /* public methods */
index 9e7a022..85ff1f0 100644 (file)
@@ -4,6 +4,7 @@
 /* 
  * Authors :
  *   Dan Winship <danw@ximian.com>
+ *   Michael Zucchi <notzed@ximian.com>
  *
  * Copyright (C) 2000 Ximian, Inc. (www.ximian.com)
  *
@@ -30,6 +31,7 @@
 #include "camel-provider.h"
 #include "camel-session.h"
 #include "camel-url.h"
+#include "camel-sasl.h"
 
 CamelProviderConfEntry pop3_conf_entries[] = {
        { CAMEL_PROVIDER_CONF_SECTION_START, NULL, NULL,
@@ -45,7 +47,7 @@ CamelProviderConfEntry pop3_conf_entries[] = {
 };
 
 static CamelProvider pop3_provider = {
-       "pop",
+       "pop3",
        
        N_("POP"),
        
@@ -84,32 +86,22 @@ CamelServiceAuthType camel_pop3_apop_authtype = {
        TRUE
 };
 
-#ifdef HAVE_KRB4
-CamelServiceAuthType camel_pop3_kpop_authtype = {
-       "Kerberos 4 (KPOP)",
-
-       N_("This will connect to the POP server and use Kerberos 4 "
-          "to authenticate to it."),
-
-       "+KPOP",
-       FALSE
-};
-#endif
-
 void
 camel_provider_module_init (CamelSession *session)
 {
-       pop3_provider.object_types[CAMEL_PROVIDER_STORE] =
-               camel_pop3_store_get_type ();
-       pop3_provider.service_cache = g_hash_table_new (camel_url_hash, camel_url_equal);
+       CamelServiceAuthType *auth;
+
+       pop3_provider.object_types[CAMEL_PROVIDER_STORE] = camel_pop3_store_get_type();
+       pop3_provider.service_cache = g_hash_table_new(camel_url_hash, camel_url_equal);
        pop3_provider.url_hash = camel_url_hash;
        pop3_provider.url_equal = camel_url_equal;
-       
-#ifdef HAVE_KRB4
-       pop3_provider.authtypes = g_list_prepend (camel_remote_store_authtype_list (), &camel_pop3_kpop_authtype);
-#endif
-       pop3_provider.authtypes = g_list_prepend (pop3_provider.authtypes, &camel_pop3_apop_authtype);
-       pop3_provider.authtypes = g_list_prepend (pop3_provider.authtypes, &camel_pop3_password_authtype);
 
-       camel_session_register_provider (session, &pop3_provider);
+       pop3_provider.authtypes = g_list_concat(camel_remote_store_authtype_list(), camel_sasl_authtype_list(FALSE));
+       auth = camel_sasl_authtype("LOGIN");
+       if (auth)
+               pop3_provider.authtypes = g_list_prepend(pop3_provider.authtypes, auth);
+       pop3_provider.authtypes = g_list_prepend(pop3_provider.authtypes, &camel_pop3_apop_authtype);
+       pop3_provider.authtypes = g_list_prepend(pop3_provider.authtypes, &camel_pop3_password_authtype);
+
+       camel_session_register_provider(session, &pop3_provider);
 }
index 6ff88d6..520d758 100644 (file)
@@ -4,8 +4,9 @@
 /* 
  * Authors:
  *   Dan Winship <danw@ximian.com>
+ *   Michael Zucchi <notzed@ximian.com>
  *
- * Copyright (C) 2000 Ximian, Inc. (www.ximian.com)
+ * Copyright (C) 2000-2002 Ximian, Inc. (www.ximian.com)
  *
  * This program is free software; you can redistribute it and/or 
  * modify it under the terms of version 2 of the GNU General Public 
 
 #include "camel-operation.h"
 
-#ifdef HAVE_KRB4
-/* Specified nowhere */
-#define KPOP_PORT 1109
-
-#include <krb.h>
-/* MIT krb4 des.h #defines _. Sigh. We don't need it. */
-#undef _
-
-#ifdef NEED_KRB_SENDAUTH_PROTO
-extern int krb_sendauth(long options, int fd, KTEXT ticket, char *service,
-                       char *inst, char *realm, unsigned KRB4_32 checksum,
-                       MSG_DAT *msg_data, CREDENTIALS *cred,
-                       Key_schedule schedule, struct sockaddr_in *laddr,
-                       struct sockaddr_in *faddr, char *version);
-#endif
-#endif
-
 #include "camel-pop3-store.h"
 #include "camel-pop3-folder.h"
 #include "camel-stream-buffer.h"
@@ -63,6 +47,9 @@ extern int krb_sendauth(long options, int fd, KTEXT ticket, char *service,
 #include "camel-exception.h"
 #include "camel-url.h"
 #include "e-util/md5-utils.h"
+#include "camel-pop3-engine.h"
+#include "camel-sasl.h"
+#include "camel-data-cache.h"
 
 /* Specified in RFC 1939 */
 #define POP3_PORT 110
@@ -81,11 +68,8 @@ static CamelFolder *get_folder (CamelStore *store, const char *folder_name,
 static void init_trash (CamelStore *store);
 static CamelFolder *get_trash  (CamelStore *store, CamelException *ex);
 
-static int pop3_get_response (CamelPop3Store *store, char **ret, CamelException *ex);
-
-
 static void
-camel_pop3_store_class_init (CamelPop3StoreClass *camel_pop3_store_class)
+camel_pop3_store_class_init (CamelPOP3StoreClass *camel_pop3_store_class)
 {
        CamelServiceClass *camel_service_class =
                CAMEL_SERVICE_CLASS (camel_pop3_store_class);
@@ -112,7 +96,7 @@ camel_pop3_store_init (gpointer object, gpointer klass)
 {
        CamelRemoteStore *remote_store = CAMEL_REMOTE_STORE (object);
 
-       remote_store->default_port = 110;
+       remote_store->default_port = POP3_PORT;
        /* FIXME: what should this port be?? */
        remote_store->default_ssl_port = 995;
 }
@@ -123,9 +107,9 @@ camel_pop3_store_get_type (void)
        static CamelType camel_pop3_store_type = CAMEL_INVALID_TYPE;
 
        if (!camel_pop3_store_type) {
-               camel_pop3_store_type = camel_type_register (CAMEL_REMOTE_STORE_TYPE, "CamelPop3Store",
-                                                            sizeof (CamelPop3Store),
-                                                            sizeof (CamelPop3StoreClass),
+               camel_pop3_store_type = camel_type_register (CAMEL_REMOTE_STORE_TYPE, "CamelPOP3Store",
+                                                            sizeof (CamelPOP3Store),
+                                                            sizeof (CamelPOP3StoreClass),
                                                             (CamelObjectClassInitFunc) camel_pop3_store_class_init,
                                                             NULL,
                                                             (CamelObjectInitFunc) camel_pop3_store_init,
@@ -138,189 +122,57 @@ camel_pop3_store_get_type (void)
 static void
 finalize (CamelObject *object)
 {
-       CamelPop3Store *pop3_store = CAMEL_POP3_STORE (object);
+       CamelPOP3Store *pop3_store = CAMEL_POP3_STORE (object);
+
+       /* force disconnect so we dont have it run later, after we've cleaned up some stuff */
+       /* SIGH */
+
+       camel_service_disconnect((CamelService *)pop3_store, TRUE, NULL);
 
-       if (pop3_store->apop_timestamp)
-               g_free (pop3_store->apop_timestamp);
-       if (pop3_store->implementation)
-               g_free (pop3_store->implementation);
+       if (pop3_store->engine)
+               camel_object_unref((CamelObject *)pop3_store->engine);
+       if (pop3_store->cache)
+               camel_object_unref((CamelObject *)pop3_store->cache);
 }
 
 static gboolean
 connect_to_server (CamelService *service, CamelException *ex)
 {
-       CamelPop3Store *store = CAMEL_POP3_STORE (service);
-       char *buf, *apoptime, *apopend;
-       int status;
+       CamelPOP3Store *store = CAMEL_POP3_STORE (service);
        gboolean result;
 
-#ifdef HAVE_KRB4
-       gboolean set_port = FALSE, kpop;
-
-       kpop = (service->url->authmech &&
-               !strcmp (service->url->authmech, "+KPOP"));
-
-       if (kpop && service->url->port == 0) {
-               set_port = TRUE;
-               service->url->port = KPOP_PORT;
-       }
-#endif
-
        result = CAMEL_SERVICE_CLASS (parent_class)->connect (service, ex);
 
-#ifdef HAVE_KRB4
-       if (set_port)
-               service->url->port = 0;
-#endif
-
        if (result == FALSE)
                return FALSE;
 
-#ifdef HAVE_KRB4
-       if (kpop) {
-               KTEXT_ST ticket_st;
-               MSG_DAT msg_data;
-               CREDENTIALS cred;
-               Key_schedule schedule;
-               struct hostent *h;
-               int fd;
-
-               h = camel_service_gethost (service, ex);
-
-               fd = GPOINTER_TO_INT (camel_tcp_stream_get_socket (CAMEL_TCP_STREAM (CAMEL_REMOTE_STORE (service)->ostream)));
-               status = krb_sendauth (0, fd, &ticket_st, "pop", h->h_name,
-                                      krb_realmofhost (h->h_name), 0,
-                                      &msg_data, &cred, schedule,
-                                      NULL, NULL, "KPOPV0.1");
-               camel_free_host (h);
-               if (status != KSUCCESS) {
-                       camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
-                                             _("Could not authenticate to "
-                                               "KPOP server: %s"),
-                                             krb_err_txt[status]);
-                       return FALSE;
-               }
-
-               if (!service->url->passwd)
-                       service->url->passwd = g_strdup (service->url->user);
-       }
-#endif /* HAVE_KRB4 */
-
-       /* Read the greeting, check status */
-       status = pop3_get_response (store, &buf, ex);
-       switch (status) {
-       case CAMEL_POP3_ERR:
-               camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
-                                     _("Could not connect to server: %s"),
-                                     buf);
-               g_free (buf);
-               /* fall through */
-       case CAMEL_POP3_FAIL:
-               return FALSE;
-       }
-
-       if (buf) {
-               apoptime = strchr (buf, '<');
-               apopend = apoptime ? strchr (apoptime, '>') : NULL;
-               if (apopend) {
-                       store->apop_timestamp =
-                               g_strndup (apoptime, apopend - apoptime + 1);
-                       memmove (apoptime, apopend + 1, strlen (apopend + 1));
-               }
-               store->implementation = buf;
-       }
-
-       /* Check extensions */
-       store->login_delay = -1;
-       store->supports_top = -1;
-       store->supports_uidl = -1;
-       store->expires = -1;
+       store->engine = camel_pop3_engine_new(CAMEL_REMOTE_STORE(store)->ostream);
 
-       status = camel_pop3_command (store, NULL, ex, "CAPA");
-       if (status == CAMEL_POP3_OK) {
-               char *p;
-               int len;
-
-               buf = camel_pop3_command_get_additional_data (store, 0, ex);
-               if (camel_exception_is_set (ex))
-                       return FALSE;
-
-               p = buf;
-               while (*p) {
-                       len = strcspn (p, "\n");
-                       if (!strncmp (p, "IMPLEMENTATION ", 15)) {
-                               g_free (store->implementation);
-                               store->implementation =
-                                       g_strndup (p + 15, len - 15);
-                       } else if (len == 3 && !strncmp (p, "TOP", 3))
-                               store->supports_top = TRUE;
-                       else if (len == 4 && !strncmp (p, "UIDL", 4))
-                               store->supports_uidl = TRUE;
-                       else if (!strncmp (p, "LOGIN-DELAY ", 12))
-                               store->login_delay = atoi (p + 12);
-                       else if (!strncmp (p, "EXPIRE NEVER", 12))
-                               store->expires = FALSE;
-                       else if (!strncmp (p, "EXPIRE ", 7))
-                               store->expires = TRUE;
-
-                       p += len;
-                       if (*p)
-                               p++;
-               }
-
-               g_free (buf);
-       }
-
-       return TRUE;
+       return store->engine != NULL;
 }
 
 extern CamelServiceAuthType camel_pop3_password_authtype;
 extern CamelServiceAuthType camel_pop3_apop_authtype;
-#ifdef HAVE_KRB4
-extern CamelServiceAuthType camel_pop3_kpop_authtype;
-#endif
 
 static GList *
 query_auth_types (CamelService *service, CamelException *ex)
 {
-       CamelPop3Store *store = CAMEL_POP3_STORE (service);
+       CamelPOP3Store *store = CAMEL_POP3_STORE (service);
        GList *types = NULL;
-       gboolean passwd = TRUE, apop = TRUE;
-#ifdef HAVE_KRB4
-       gboolean kpop;
-#endif
 
         types = CAMEL_SERVICE_CLASS (parent_class)->query_auth_types (service, ex);
        if (camel_exception_is_set (ex))
                return types;
 
-       passwd = connect_to_server (service, NULL);
-       apop = store->apop_timestamp != NULL;
-       if (passwd)
+       if (connect_to_server (service, NULL)) {
+               types = g_list_concat(types, g_list_copy(store->engine->auth));
                pop3_disconnect (service, TRUE, NULL);
-
-#ifdef HAVE_KRB4
-       service->url->authmech = "+KPOP";
-       kpop = connect_to_server (service, NULL);
-       service->url->authmech = NULL;
-       if (kpop)
-               pop3_disconnect (service, TRUE, NULL);
-#endif
-
-       if (passwd)
-               types = g_list_append (types, &camel_pop3_password_authtype);
-       if (apop)
-               types = g_list_append (types, &camel_pop3_apop_authtype);
-#ifdef HAVE_KRB4
-       if (kpop)
-               types = g_list_append (types, &camel_pop3_kpop_authtype);
-#endif
-
-       if (!types) {
+       } else {
                camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
                                      _("Could not connect to POP server on "
                                        "%s."), service->url->host);
        }
+
        return types;
 }
 
@@ -334,31 +186,95 @@ query_auth_types (CamelService *service, CamelException *ex)
  * reconnect.
  **/
 void
-camel_pop3_store_expunge (CamelPop3Store *store, CamelException *ex)
+camel_pop3_store_expunge (CamelPOP3Store *store, CamelException *ex)
 {
-       camel_pop3_command (store, NULL, ex, "QUIT");
+       CamelPOP3Command *pc;
+
+       pc = camel_pop3_engine_command_new(store->engine, 0, NULL, NULL, "QUIT\r\n");
+       while (camel_pop3_engine_iterate(store->engine, NULL) > 0)
+               ;
+       camel_pop3_engine_command_free(store->engine, pc);
+
        camel_service_disconnect (CAMEL_SERVICE (store), FALSE, ex);
 }
 
+static int
+try_sasl(CamelPOP3Store *store, const char *mech, CamelException *ex)
+{
+       CamelPOP3Stream *stream = store->engine->stream;
+       unsigned char *line, *resp;
+       CamelSasl *sasl;
+       unsigned int len;
+       int ret;
+
+       sasl = camel_sasl_new("pop3", mech, (CamelService *)store);
+       if (sasl == NULL) {
+               camel_exception_set (ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE,
+                                    _("Unable to connect to POP server.\n"
+                                      "No support for requested "
+                                      "authentication mechanism."));
+               return -1;
+       }
+
+       if (camel_stream_printf((CamelStream *)stream, "AUTH %s\r\n", mech) == -1)
+               goto ioerror;
+
+       while (1) {
+               if (camel_pop3_stream_line(stream, &line, &len) == -1)
+                       goto ioerror;
+               if (strncmp(line, "+OK", 3) == 0)
+                       break;
+               if (strncmp(line, "-ERR", 4) == 0) {
+                       camel_exception_setv(ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE,
+                                            _("SASL `%s' Login failed: %s"), mech, line);
+                       goto done;
+               }
+               /* If we dont get continuation, or the sasl object's run out of work, or we dont get a challenge,
+                  its a protocol error, so fail, and try reset the server */
+               if (strncmp(line, "+ ", 2) != 0
+                   || camel_sasl_authenticated(sasl)
+                   || (resp = camel_sasl_challenge_base64(sasl, line+2, ex)) == NULL) {
+                       camel_stream_printf((CamelStream *)stream, "*\r\n");
+                       camel_pop3_stream_line(stream, &line, &len);
+                       camel_exception_setv(ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE,
+                                            _("SASL Protocol error"));
+                       goto done;
+               }
+
+               ret = camel_stream_printf((CamelStream *)stream, "%s\r\n", resp);
+               g_free(resp);
+               if (ret == -1)
+                       goto ioerror;
+
+       }
+       camel_object_unref((CamelObject *)sasl);
+       return 0;
+
+ioerror:
+       camel_exception_setv(ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE,
+                            _("I/O Error: %s"), strerror(errno));
+done:
+       camel_object_unref((CamelObject *)sasl);
+       return -1;
+}
 
 static gboolean
 pop3_try_authenticate (CamelService *service, const char *errmsg,
                       CamelException *ex)
 {
-       CamelPop3Store *store = (CamelPop3Store *)service;
+       CamelPOP3Store *store = (CamelPOP3Store *)service;
        int status;
-       char *msg;
-       
-       /* The KPOP code will have set the password to be the username
-        * in connect_to_server. Password and APOP are the only other
-        * cases, and they both need a password. So if there's no
-        * password stored, query for it.
-        */
+       CamelPOP3Command *pcu = NULL, *pcp = NULL;
+
+       /* override, testing only */
+       /*printf("Forcing authmech to 'login'\n");
+       service->url->authmech = g_strdup("LOGIN");*/
+
        if (!service->url->passwd) {
                char *prompt;
                
-               prompt = g_strdup_printf (_("%sPlease enter the POP3 password "
-                                           "for %s@%s"), errmsg ? errmsg : "",
+               prompt = g_strdup_printf (_("%sPlease enter the POP password for %s@%s"),
+                                         errmsg ? errmsg : "",
                                          service->url->user,
                                          service->url->host);
                service->url->passwd = camel_session_get_password (camel_service_get_session (service),
@@ -367,59 +283,56 @@ pop3_try_authenticate (CamelService *service, const char *errmsg,
                if (!service->url->passwd)
                        return FALSE;
        }
-       
-       if (!service->url->authmech || !strcmp (service->url->authmech, "+KPOP")) {
-               status = camel_pop3_command (store, &msg, ex, "USER %s",
-                                            service->url->user);
-               switch (status) {
-               case CAMEL_POP3_ERR:
-                       camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE,
-                                             _("Unable to connect to POP "
-                                               "server.\nError sending "
-                                               "username: %s"),
-                                             msg ? msg : _("(Unknown)"));
-                       g_free (msg);
-                       /*fallll*/
-               case CAMEL_POP3_FAIL:
-                       return FALSE;
-               }
-               g_free (msg);
-               
-               status = camel_pop3_command (store, &msg, ex, "PASS %s",
-                                            service->url->passwd);
-       } else if (!strcmp (service->url->authmech, "+APOP")
-                  && store->apop_timestamp) {
+
+       if (!service->url->authmech) {
+               /* pop engine will take care of pipelining ability */
+               pcu = camel_pop3_engine_command_new(store->engine, 0, NULL, NULL, "USER %s\r\n", service->url->user);
+               pcp = camel_pop3_engine_command_new(store->engine, 0, NULL, NULL, "PASS %s\r\n", service->url->passwd);
+       } else if (strcmp(service->url->authmech, "+APOP") == 0 && store->engine->apop) {
                char *secret, md5asc[33], *d;
                unsigned char md5sum[16], *s;
                
-               secret = g_strdup_printf ("%s%s", store->apop_timestamp,
-                                         service->url->passwd);
-               md5_get_digest (secret, strlen (secret), md5sum);
-               g_free (secret);
-               
+               secret = alloca(strlen(store->engine->apop)+strlen(service->url->passwd)+1);
+               sprintf(secret, "%s%s",  store->engine->apop, service->url->passwd);
+               md5_get_digest(secret, strlen (secret), md5sum);
+
                for (s = md5sum, d = md5asc; d < md5asc + 32; s++, d += 2)
                        sprintf (d, "%.2x", *s);
                
-               status = camel_pop3_command (store, &msg, ex, "APOP %s %s",
-                                            service->url->user, md5asc);
+               pcp = camel_pop3_engine_command_new(store->engine, 0, NULL, NULL, "APOP %s %s\r\n", service->url->user, md5asc);
        } else {
+               CamelServiceAuthType *auth;
+               GList *l;
+
+               l = store->engine->auth;
+               while (l) {
+                       auth = l->data;
+                       if (strcmp(auth->authproto, service->url->authmech) == 0) {
+                               return try_sasl(store, service->url->authmech, ex) == -1;
+                       }
+               }
+
                camel_exception_set (ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE,
                                     _("Unable to connect to POP server.\n"
                                       "No support for requested "
                                       "authentication mechanism."));
                return FALSE;
        }
-       
-       if (status == CAMEL_POP3_ERR) {
-               camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE,
-                                     _("Unable to connect to POP server.\n"
-                                       "Error sending password: %s"),
-                                     msg ? msg : _("(Unknown)"));
+
+       while (camel_pop3_engine_iterate(store->engine, pcp) > 0)
+               ;
+       status = pcp->state != CAMEL_POP3_COMMAND_OK;
+       if (status) {
+               camel_exception_setv(ex,  CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE,
+                                    _("Unable to connect to POP server.\nError sending password: %s"),
+                                    store->engine->line);
        }
-       
-       g_free (msg);
-       
-       return status == CAMEL_POP3_ERR;
+       camel_pop3_engine_command_free(store->engine, pcp);
+
+       if (pcu)
+               camel_pop3_engine_command_free(store->engine, pcu);
+
+       return status;
 }
 
 static gboolean
@@ -427,6 +340,22 @@ pop3_connect (CamelService *service, CamelException *ex)
 {
        char *errbuf = NULL;
        gboolean tryagain;
+       CamelPOP3Store *store = (CamelPOP3Store *)service;
+
+       if (store->cache == NULL) {
+               char *root;
+
+               root = camel_session_get_storage_path(service->session, service, ex);
+               if (root) {
+                       store->cache = camel_data_cache_new(root, 0, ex);
+                       g_free(root);
+                       if (store->cache) {
+                               /* Default cache expiry - 1 week or not visited in a day */
+                               camel_data_cache_set_expire_age(store->cache, 60*60*24*7);
+                               camel_data_cache_set_expire_access(store->cache, 60*60*24);
+                       }
+               }
+       }
        
        if (!connect_to_server (service, ex))
                return FALSE;
@@ -460,10 +389,19 @@ pop3_connect (CamelService *service, CamelException *ex)
 static gboolean
 pop3_disconnect (CamelService *service, gboolean clean, CamelException *ex)
 {
-       CamelPop3Store *store = CAMEL_POP3_STORE (service);
+       CamelPOP3Store *store = CAMEL_POP3_STORE (service);
        
-       if (clean)
-               camel_pop3_command (store, NULL, ex, "QUIT");
+       if (clean) {
+               CamelPOP3Command *pc;
+
+               pc = camel_pop3_engine_command_new(store->engine, 0, NULL, NULL, "QUIT\r\n");
+               while (camel_pop3_engine_iterate(store->engine, NULL) > 0)
+                       ;
+               camel_pop3_engine_command_free(store->engine, pc);
+       }
+
+       camel_object_unref((CamelObject *)store->engine);
+       store->engine = NULL;
        
        if (!CAMEL_SERVICE_CLASS (parent_class)->disconnect (service, clean, ex))
                return FALSE;
@@ -472,10 +410,9 @@ pop3_disconnect (CamelService *service, gboolean clean, CamelException *ex)
 }
 
 static CamelFolder *
-get_folder (CamelStore *store, const char *folder_name,
-           guint32 flags, CamelException *ex)
+get_folder (CamelStore *store, const char *folder_name, guint32 flags, CamelException *ex)
 {
-       if (g_strcasecmp (folder_name, "inbox") != 0) {
+       if (strcasecmp (folder_name, "inbox") != 0) {
                camel_exception_setv (ex, CAMEL_EXCEPTION_FOLDER_INVALID,
                                      _("No such folder `%s'."), folder_name);
                return NULL;
@@ -496,155 +433,3 @@ get_trash (CamelStore *store, CamelException *ex)
        /* no-op */
        return NULL;
 }
-
-
-/**
- * camel_pop3_command: Send a command to a POP3 server.
- * @store: the POP3 store
- * @ret: a pointer to return the full server response in
- * @fmt: a printf-style format string, followed by arguments
- *
- * This command sends the command specified by @fmt and the following
- * arguments to the connected POP3 store specified by @store. It then
- * reads the server's response and parses out the status code. If
- * the caller passed a non-NULL pointer for @ret, camel_pop3_command
- * will set it to point to an buffer containing the rest of the
- * response from the POP3 server. (If @ret was passed but there was
- * no extended response, @ret will be set to NULL.) The caller must
- * free this buffer when it is done with it.
- *
- * Return value: one of CAMEL_POP3_OK (command executed successfully),
- * CAMEL_POP3_ERR (command encounted an error), or CAMEL_POP3_FAIL
- * (a protocol-level error occurred, and Camel is uncertain of the
- * result of the command.) @ex will be set if the return value is
- * CAMEL_POP3_FAIL, but *NOT* if it is CAMEL_POP3_ERR.
- **/
-int
-camel_pop3_command (CamelPop3Store *store, char **ret, CamelException *ex, char *fmt, ...)
-{
-       char *cmdbuf;
-       va_list ap;
-       
-       va_start (ap, fmt);
-       cmdbuf = g_strdup_vprintf (fmt, ap);
-       va_end (ap);
-       
-       /* Send the command */
-       if (camel_remote_store_send_string (CAMEL_REMOTE_STORE (store), ex, "%s\r\n", cmdbuf) < 0) {
-               g_free (cmdbuf);
-               if (ret)
-                       *ret = NULL;
-               return CAMEL_POP3_FAIL;
-       }
-       g_free (cmdbuf);
-       
-       return pop3_get_response (store, ret, ex);
-}
-
-static int
-pop3_get_response (CamelPop3Store *store, char **ret, CamelException *ex)
-{
-       char *respbuf;
-       int status;
-       
-       if (camel_remote_store_recv_line (CAMEL_REMOTE_STORE (store), &respbuf, ex) < 0) {
-               if (ret)
-                       *ret = NULL;
-               return CAMEL_POP3_FAIL;
-       }
-       
-       if (!strncmp (respbuf, "+OK", 3))
-               status = CAMEL_POP3_OK;
-       else if (!strncmp (respbuf, "-ERR", 4))
-               status = CAMEL_POP3_ERR;
-       else {
-               status = CAMEL_POP3_FAIL;
-               camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
-                                     _("Unexpected response from POP server: %s"),
-                                     respbuf);
-       }
-       
-       if (ret) {
-               if (status != CAMEL_POP3_FAIL) {
-                       *ret = strchr (respbuf, ' ');
-                       if (*ret)
-                               *ret = g_strdup (*ret + 1);
-               } else
-                       *ret = NULL;
-       }
-       g_free (respbuf);
-       
-       return status;
-}
-
-/**
- * camel_pop3_command_get_additional_data: get "additional data" from
- * a POP3 command.
- * @store: the POP3 store
- * @total: Total bytes expected (for progress reporting), use 0 for 'unknown'.
- *
- * This command gets the additional data returned by "multi-line" POP
- * commands, such as LIST, RETR, TOP, and UIDL. This command _must_
- * be called after a successful (CAMEL_POP3_OK) call to
- * camel_pop3_command for a command that has a multi-line response.
- * The returned data is un-byte-stuffed, and has lines termined by
- * newlines rather than CR/LF pairs.
- *
- * Return value: the data, which the caller must free.
- **/
-char *
-camel_pop3_command_get_additional_data (CamelPop3Store *store, int total, CamelException *ex)
-{
-       GPtrArray *data;
-       char *buf, *p;
-       int i, len = 0, status = CAMEL_POP3_OK;
-       int pc = 0;
-
-       data = g_ptr_array_new ();
-       while (1) {
-               if (camel_remote_store_recv_line (CAMEL_REMOTE_STORE (store), &buf, ex) < 0) {
-                       status = CAMEL_POP3_FAIL;
-                       break;
-               }
-
-               if (!strcmp (buf, "."))
-                       break;
-
-               g_ptr_array_add (data, buf);
-               len += strlen (buf) + 1;
-
-               if (total) {
-                       pc = (len+1) * 100 / total;
-                       camel_operation_progress(NULL, pc);
-               } else {
-                       camel_operation_progress_count(NULL, len);
-               }
-       }
-       
-       if (buf)
-               g_free (buf);
-
-       if (status == CAMEL_POP3_OK) {
-               buf = g_malloc0 (len + 1);
-
-               for (i = 0, p = buf; i < data->len; i++) {
-                       char *ptr, *datap;
-
-                       datap = (char *) data->pdata[i];
-                       ptr = (*datap == '.') ? datap + 1 : datap;
-                       len = strlen (ptr);
-                       memcpy (p, ptr, len);
-                       p += len;
-                       *p++ = '\n';
-               }
-               *p = '\0';
-       } else
-               buf = NULL;
-
-       for (i = 0; i < data->len; i++)
-               g_free (data->pdata[i]);
-       g_ptr_array_free (data, TRUE);
-
-       return buf;
-}
-
index fcc488f..8d560f8 100644 (file)
@@ -4,8 +4,9 @@
 /* 
  * Authors:
  *   Dan Winship <danw@ximian.com>
+ *   Michael Zucchi <notzed@ximian.com>
  *
- * Copyright (C) 2000 Ximian, Inc. (www.ximian.com)
+ * Copyright (C) 2000-2002 Ximian, Inc. (www.ximian.com)
  *
  * This program is free software; you can redistribute it and/or 
  * modify it under the terms of version 2 of the GNU General Public 
@@ -32,39 +33,39 @@ extern "C" {
 #pragma }
 #endif /* __cplusplus }*/
 
-#include "camel-types.h"
-#include "camel-remote-store.h"
+#include <camel/camel-types.h>
+#include <camel/camel-remote-store.h>
+#include "camel-pop3-engine.h"
 
 #define CAMEL_POP3_STORE_TYPE     (camel_pop3_store_get_type ())
-#define CAMEL_POP3_STORE(obj)     (CAMEL_CHECK_CAST((obj), CAMEL_POP3_STORE_TYPE, CamelPop3Store))
-#define CAMEL_POP3_STORE_CLASS(k) (CAMEL_CHECK_CLASS_CAST ((k), CAMEL_POP3_STORE_TYPE, CamelPop3StoreClass))
+#define CAMEL_POP3_STORE(obj)     (CAMEL_CHECK_CAST((obj), CAMEL_POP3_STORE_TYPE, CamelPOP3Store))
+#define CAMEL_POP3_STORE_CLASS(k) (CAMEL_CHECK_CLASS_CAST ((k), CAMEL_POP3_STORE_TYPE, CamelPOP3StoreClass))
 #define CAMEL_IS_POP3_STORE(o)    (CAMEL_CHECK_TYPE((o), CAMEL_POP3_STORE_TYPE))
 
 
 typedef struct {
        CamelRemoteStore parent_object;
 
-       char *apop_timestamp, *implementation;
-       gboolean supports_top, supports_uidl, expires;
-       int login_delay;
+       CamelPOP3Engine *engine; /* pop processing engine */
 
-} CamelPop3Store;
+       struct _CamelDataCache *cache;
+} CamelPOP3Store;
 
 
 
 typedef struct {
        CamelRemoteStoreClass parent_class;
 
-} CamelPop3StoreClass;
+} CamelPOP3StoreClass;
 
 
 /* public methods */
-void camel_pop3_store_expunge (CamelPop3Store *store, CamelException *ex);
+void camel_pop3_store_expunge (CamelPOP3Store *store, CamelException *ex);
 
 /* support functions */
 enum { CAMEL_POP3_OK, CAMEL_POP3_ERR, CAMEL_POP3_FAIL };
-int camel_pop3_command (CamelPop3Store *store, char **ret, CamelException *ex, char *fmt, ...);
-char *camel_pop3_command_get_additional_data (CamelPop3Store *store, int total, CamelException *ex);
+int camel_pop3_command (CamelPOP3Store *store, char **ret, CamelException *ex, char *fmt, ...);
+char *camel_pop3_command_get_additional_data (CamelPOP3Store *store, int total, CamelException *ex);
 
 /* Standard Camel function */
 CamelType camel_pop3_store_get_type (void);
diff --git a/camel/providers/pop3/camel-pop3-stream.c b/camel/providers/pop3/camel-pop3-stream.c
new file mode 100644 (file)
index 0000000..5b0dd97
--- /dev/null
@@ -0,0 +1,468 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; fill-column: 160 -*-
+ *
+ * Author:
+ *  Michael Zucchi <notzed@ximian.com>
+ *
+ * Copyright 2002 Ximian, Inc. (www.ximian.com)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+/* This is *identical* to the camel-nntp-stream, so should probably
+   work out a way to merge them */
+
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <errno.h>
+
+#include <string.h>
+#include <stdio.h>
+
+#include <glib.h>
+
+#include "camel-pop3-stream.h"
+
+extern int camel_verbose_debug;
+#define dd(x) (camel_verbose_debug?(x):0)
+
+static CamelObjectClass *parent_class = NULL;
+
+/* Returns the class for a CamelStream */
+#define CS_CLASS(so) CAMEL_POP3_STREAM_CLASS(CAMEL_OBJECT_GET_CLASS(so))
+
+#define CAMEL_POP3_STREAM_SIZE (4096)
+#define CAMEL_POP3_STREAM_LINE (1024) /* maximum line size */
+
+static int
+stream_fill(CamelPOP3Stream *is)
+{
+       int left = 0;
+
+       if (is->source) {
+               left = is->end - is->ptr;
+               memcpy(is->buf, is->ptr, left);
+               is->end = is->buf + left;
+               is->ptr = is->buf;
+               left = camel_stream_read(is->source, is->end, CAMEL_POP3_STREAM_SIZE - (is->end - is->buf));
+               if (left > 0) {
+                       is->end += left;
+                       is->end[0] = '\n';
+                       return is->end - is->ptr;
+               } else {
+                       dd(printf("POP3_STREAM_FILL(ERROR): '%s'\n", strerror(errno)));
+                       return -1;
+               }
+       }
+
+       return 0;
+}
+
+static ssize_t
+stream_read(CamelStream *stream, char *buffer, size_t n)
+{
+       CamelPOP3Stream *is = (CamelPOP3Stream *)stream;
+       char *o, *oe;
+       unsigned char *p, *e, c;
+       int state;
+
+       if (is->mode != CAMEL_POP3_STREAM_DATA || n == 0)
+               return 0;
+
+       o = buffer;
+       oe = buffer + n;
+       state = is->state;
+
+       /* Need to copy/strip '.'s and whatnot */
+       p = is->ptr;
+       e = is->end;
+
+       switch(state) {
+       state_0:
+       case 0:         /* start of line, always read at least 3 chars */
+               while (e - p < 3) {
+                       is->ptr = p;
+                       if (stream_fill(is) == -1)
+                               return -1;
+                       p = is->ptr;
+                       e = is->end;
+               }
+               if (p[0] == '.') {
+                       if (p[1] == '\r' && p[2] == '\n') {
+                               is->ptr = p+3;
+                               is->mode = CAMEL_POP3_STREAM_EOD;
+                               is->state = 0;
+                               dd(printf("POP3_STREAM_READ(%d):\n%.*s\n", o-buffer, o-buffer, buffer));
+                               return o-buffer;
+                       }
+                       p++;
+               }
+               state = 1;
+               /* FALLS THROUGH */
+       case 1:         /* looking for next sol */
+               while (o < oe) {
+                       c = *p++;
+                       if (c == '\n') {
+                               /* end of input sentinal check */
+                               if (p > e) {
+                                       is->ptr = e;
+                                       if (stream_fill(is) == -1)
+                                               return -1;
+                                       p = is->ptr;
+                                       e = is->end;
+                               } else {
+                                       *o++ = '\n';
+                                       state = 0;
+                                       goto state_0;
+                               }
+                       } else if (c != '\r') {
+                               *o++ = c;
+                       }
+               }
+               break;
+       }
+
+       is->ptr = p;
+       is->state = state;
+
+       dd(printf("POP3_STREAM_READ(%d):\n%.*s\n", o-buffer, o-buffer, buffer));
+
+       return o-buffer;
+}
+
+static ssize_t
+stream_write(CamelStream *stream, const char *buffer, size_t n)
+{
+       CamelPOP3Stream *is = (CamelPOP3Stream *)stream;
+
+       dd(printf("POP3_STREAM_WRITE(%d):\n%.*s\n", n, (int)n, buffer));
+
+       return camel_stream_write(is->source, buffer, n);
+}
+
+static int
+stream_close(CamelStream *stream)
+{
+       /* nop? */
+       return 0;
+}
+
+static int
+stream_flush(CamelStream *stream)
+{
+       /* nop? */
+       return 0;
+}
+
+static gboolean
+stream_eos(CamelStream *stream)
+{
+       CamelPOP3Stream *is = (CamelPOP3Stream *)stream;
+
+       return is->mode != CAMEL_POP3_STREAM_DATA;
+}
+
+static int
+stream_reset(CamelStream *stream)
+{
+       /* nop?  reset literal mode? */
+       return 0;
+}
+
+static void
+camel_pop3_stream_class_init (CamelStreamClass *camel_pop3_stream_class)
+{
+       CamelStreamClass *camel_stream_class = (CamelStreamClass *)camel_pop3_stream_class;
+
+       parent_class = camel_type_get_global_classfuncs( CAMEL_OBJECT_TYPE );
+
+       /* virtual method definition */
+       camel_stream_class->read = stream_read;
+       camel_stream_class->write = stream_write;
+       camel_stream_class->close = stream_close;
+       camel_stream_class->flush = stream_flush;
+       camel_stream_class->eos = stream_eos;
+       camel_stream_class->reset = stream_reset;
+}
+
+static void
+camel_pop3_stream_init(CamelPOP3Stream *is, CamelPOP3StreamClass *isclass)
+{
+       /* +1 is room for appending a 0 if we need to for a line */
+       is->ptr = is->end = is->buf = g_malloc(CAMEL_POP3_STREAM_SIZE+1);
+       is->lineptr = is->linebuf = g_malloc(CAMEL_POP3_STREAM_LINE+1);
+       is->lineend = is->linebuf + CAMEL_POP3_STREAM_LINE;
+
+       /* init sentinal */
+       is->ptr[0] = '\n';
+
+       is->state = 0;
+       is->mode = CAMEL_POP3_STREAM_LINE;
+}
+
+static void
+camel_pop3_stream_finalise(CamelPOP3Stream *is)
+{
+       g_free(is->buf);
+       g_free(is->linebuf);
+       if (is->source)
+               camel_object_unref((CamelObject *)is->source);
+}
+
+CamelType
+camel_pop3_stream_get_type (void)
+{
+       static CamelType camel_pop3_stream_type = CAMEL_INVALID_TYPE;
+
+       if (camel_pop3_stream_type == CAMEL_INVALID_TYPE) {
+               camel_pop3_stream_type = camel_type_register( camel_stream_get_type(),
+                                                           "CamelPOP3Stream",
+                                                           sizeof( CamelPOP3Stream ),
+                                                           sizeof( CamelPOP3StreamClass ),
+                                                           (CamelObjectClassInitFunc) camel_pop3_stream_class_init,
+                                                           NULL,
+                                                           (CamelObjectInitFunc) camel_pop3_stream_init,
+                                                           (CamelObjectFinalizeFunc) camel_pop3_stream_finalise );
+       }
+
+       return camel_pop3_stream_type;
+}
+
+/**
+ * camel_pop3_stream_new:
+ *
+ * Returns a NULL stream.  A null stream is always at eof, and
+ * always returns success for all reads and writes.
+ *
+ * Return value: the stream
+ **/
+CamelStream *
+camel_pop3_stream_new(CamelStream *source)
+{
+       CamelPOP3Stream *is;
+
+       is = (CamelPOP3Stream *)camel_object_new(camel_pop3_stream_get_type ());
+       camel_object_ref((CamelObject *)source);
+       is->source = source;
+
+       return (CamelStream *)is;
+}
+
+/* Get one line from the pop3 stream */
+int
+camel_pop3_stream_line(CamelPOP3Stream *is, unsigned char **data, unsigned int *len)
+{
+       register unsigned char c, *p, *o, *oe;
+       int newlen, oldlen;
+       unsigned char *e;
+
+       if (is->mode == CAMEL_POP3_STREAM_EOD) {
+               *data = is->linebuf;
+               *len = 0;
+               return 0;
+       }
+
+       o = is->linebuf;
+       oe = is->lineend - 1;
+       p = is->ptr;
+       e = is->end;
+
+       /* Data mode, convert leading '..' to '.', and stop when we reach a solitary '.' */
+       if (is->mode == CAMEL_POP3_STREAM_DATA) {
+               /* need at least 3 chars in buffer */
+               while (e-p < 3) {
+                       is->ptr = p;
+                       if (stream_fill(is) == -1)
+                               return -1;
+                       p = is->ptr;
+                       e = is->end;
+               }
+
+               /* check for isolated '.\r\n' or begging of line '.' */
+               if (p[0] == '.') {
+                       if (p[1] == '\r' && p[2] == '\n') {
+                               is->ptr = p+3;
+                               is->mode = CAMEL_POP3_STREAM_EOD;
+                               *data = is->linebuf;
+                               *len = 0;
+                               is->linebuf[0] = 0;
+
+                               dd(printf("POP3_STREAM_LINE(END)\n"));
+
+                               return 0;
+                       }
+                       p++;
+               }
+       }
+
+       while (1) {
+               while (o < oe) {
+                       c = *p++;
+                       if (c == '\n') {
+                               /* sentinal? */
+                               if (p> e) {
+                                       is->ptr = e;
+                                       if (stream_fill(is) == -1)
+                                               return -1;
+                                       p = is->ptr;
+                                       e = is->end;
+                               } else {
+                                       is->ptr = p;
+                                       *data = is->linebuf;
+                                       *len = o - is->linebuf;
+                                       *o = 0;
+
+                                       dd(printf("POP3_STREAM_LINE(%d): '%s'\n", *len, *data));
+
+                                       return 1;
+                               }
+                       } else if (c != '\r') {
+                               *o++ = c;
+                       }
+               }
+
+               /* limit this for bad server data? */
+               oldlen = o - is->linebuf;
+               newlen = (is->lineend - is->linebuf) * 3 / 2;
+               is->lineptr = is->linebuf = g_realloc(is->linebuf, newlen);
+               is->lineend = is->linebuf + newlen;
+               oe = is->lineend - 1;
+               o = is->linebuf + oldlen;
+       }
+
+       return -1;
+}
+
+/* returns -1 on error, 0 if last lot of data, >0 if more remaining */
+int camel_pop3_stream_gets(CamelPOP3Stream *is, unsigned char **start, unsigned int *len)
+{
+       int max;
+       unsigned char *end;
+
+       *len = 0;
+
+       max = is->end - is->ptr;
+       if (max == 0) {
+               max = stream_fill(is);
+               if (max <= 0)
+                       return max;
+       }
+
+       *start = is->ptr;
+       end = memchr(is->ptr, '\n', max);
+       if (end)
+               max = (end - is->ptr) + 1;
+       *start = is->ptr;
+       *len = max;
+       is->ptr += max;
+
+       dd(printf("POP3_STREAM_GETS(%s,%d): '%.*s'\n", end==NULL?"more":"last", *len, (int)*len, *start));
+
+       return end == NULL?1:0;
+}
+
+void camel_pop3_stream_set_mode(CamelPOP3Stream *is, camel_pop3_stream_mode_t mode)
+{
+       is->mode = mode;
+}
+
+/* returns -1 on erorr, 0 if last data, >0 if more data left */
+int camel_pop3_stream_getd(CamelPOP3Stream *is, unsigned char **start, unsigned int *len)
+{
+       unsigned char *p, *e, *s;
+       int state;
+
+       *len = 0;
+
+       if (is->mode == CAMEL_POP3_STREAM_EOD)
+               return 0;
+
+       if (is->mode == CAMEL_POP3_STREAM_LINE) {
+               g_warning("pop3_stream reading data in line mode\n");
+               return 0;
+       }
+
+       state = is->state;
+       p = is->ptr;
+       e = is->end;
+
+       while (e - p < 3) {
+               is->ptr = p;
+               if (stream_fill(is) == -1)
+                       return -1;
+               p = is->ptr;
+               e = is->end;
+       }
+
+       s = p;
+
+       do {
+               switch(state) {
+               case 0:
+                       /* check leading '.', ... */
+                       if (p[0] == '.') {
+                               if (p[1] == '\r' && p[2] == '\n') {
+                                       is->ptr = p+3;
+                                       *len = p-s;
+                                       *start = s;
+                                       is->mode = CAMEL_POP3_STREAM_EOD;
+                                       is->state = 0;
+
+                                       dd(printf("POP3_STREAM_GETD(%s,%d): '%.*s'\n", "last", *len, (int)*len, *start));
+
+                                       return 0;
+                               }
+
+                               /* If at start, just skip '.', else return data upto '.' but skip it */
+                               if (p == s) {
+                                       s++;
+                                       p++;
+                               } else {
+                                       is->ptr = p+1;
+                                       *len = p-s;
+                                       *start = s;
+                                       is->state = 1;
+
+                                       dd(printf("POP3_STREAM_GETD(%s,%d): '%.*s'\n", "more", *len, (int)*len, *start));
+
+                                       return 1;
+                               }
+                       }
+                       state = 1;
+               case 1:
+                       /* Scan for sentinal */
+                       while ((*p++)!='\n')
+                               ;
+
+                       if (p > e) {
+                               p = e;
+                       } else {
+                               state = 0;
+                       }
+                       break;
+               }
+       } while ((e-p) >= 3);
+
+       is->state = state;
+       is->ptr = p;
+       *len = p-s;
+       *start = s;
+
+       dd(printf("POP3_STREAM_GETD(%s,%d): '%.*s'\n", "more", *len, (int)*len, *start));
+
+       return 1;
+}
diff --git a/camel/providers/pop3/camel-pop3-stream.h b/camel/providers/pop3/camel-pop3-stream.h
new file mode 100644 (file)
index 0000000..2baf48d
--- /dev/null
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2002 Ximian Inc.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+/* This is *identical* to the camel-nntp-stream, so should probably
+   work out a way to merge them */
+
+#ifndef _CAMEL_POP3_STREAM_H
+#define _CAMEL_POP3_STREAM_H
+
+#include <camel/camel-stream.h>
+
+#define CAMEL_POP3_STREAM(obj)         CAMEL_CHECK_CAST (obj, camel_pop3_stream_get_type (), CamelPOP3Stream)
+#define CAMEL_POP3_STREAM_CLASS(klass) CAMEL_CHECK_CLASS_CAST (klass, camel_pop3_stream_get_type (), CamelPOP3StreamClass)
+#define CAMEL_IS_POP3_STREAM(obj)      CAMEL_CHECK_TYPE (obj, camel_pop3_stream_get_type ())
+
+typedef struct _CamelPOP3StreamClass CamelPOP3StreamClass;
+typedef struct _CamelPOP3Stream CamelPOP3Stream;
+
+typedef enum {
+       CAMEL_POP3_STREAM_LINE,
+       CAMEL_POP3_STREAM_DATA,
+       CAMEL_POP3_STREAM_EOD,  /* end of data, acts as if end of stream */
+} camel_pop3_stream_mode_t;
+
+struct _CamelPOP3Stream {
+       CamelStream parent;
+
+       CamelStream *source;
+
+       camel_pop3_stream_mode_t mode;
+       int state;
+
+       unsigned char *buf, *ptr, *end;
+       unsigned char *linebuf, *lineptr, *lineend;
+};
+
+struct _CamelPOP3StreamClass {
+       CamelStreamClass parent_class;
+};
+
+guint           camel_pop3_stream_get_type     (void);
+
+CamelStream     *camel_pop3_stream_new         (CamelStream *source);
+
+
+void            camel_pop3_stream_set_mode     (CamelPOP3Stream *is, camel_pop3_stream_mode_t mode);
+
+int              camel_pop3_stream_line                (CamelPOP3Stream *is, unsigned char **data, unsigned int *len);
+int             camel_pop3_stream_gets         (CamelPOP3Stream *is, unsigned char **start, unsigned int *len);
+int             camel_pop3_stream_getd         (CamelPOP3Stream *is, unsigned char **start, unsigned int *len);
+
+#endif /* ! _CAMEL_POP3_STREAM_H */