diff options
author | Not Zed <NotZed@Ximian.com> | 2002-01-30 13:14:48 +0800 |
---|---|---|
committer | Michael Zucci <zucchi@src.gnome.org> | 2002-01-30 13:14:48 +0800 |
commit | b894c24f03beeaaeb947676f95c05473ee7691d4 (patch) | |
tree | e940ee60ed72b74e034003a2d44b5bf9d3632852 | |
parent | 22d1017461bcf5c16846721fd5106abff3f7689b (diff) | |
download | gsoc2013-evolution-b894c24f03beeaaeb947676f95c05473ee7691d4.tar.gz gsoc2013-evolution-b894c24f03beeaaeb947676f95c05473ee7691d4.tar.zst gsoc2013-evolution-b894c24f03beeaaeb947676f95c05473ee7691d4.zip |
Changed name from "NT Login" to simply "Login".
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.
svn path=/trunk/; revision=15521
-rw-r--r-- | camel/ChangeLog | 20 | ||||
-rw-r--r-- | camel/camel-data-cache.c | 18 | ||||
-rw-r--r-- | camel/camel-sasl-login.c | 2 | ||||
-rw-r--r-- | camel/camel-session.c | 2 | ||||
-rw-r--r-- | camel/providers/pop3/Makefile.am | 4 | ||||
-rw-r--r-- | camel/providers/pop3/camel-pop3-engine.c | 348 | ||||
-rw-r--r-- | camel/providers/pop3/camel-pop3-engine.h | 123 | ||||
-rw-r--r-- | camel/providers/pop3/camel-pop3-folder.c | 618 | ||||
-rw-r--r-- | camel/providers/pop3/camel-pop3-folder.h | 24 | ||||
-rw-r--r-- | camel/providers/pop3/camel-pop3-provider.c | 38 | ||||
-rw-r--r-- | camel/providers/pop3/camel-pop3-store.c | 555 | ||||
-rw-r--r-- | camel/providers/pop3/camel-pop3-store.h | 27 | ||||
-rw-r--r-- | camel/providers/pop3/camel-pop3-stream.c | 468 | ||||
-rw-r--r-- | camel/providers/pop3/camel-pop3-stream.h | 69 |
14 files changed, 1645 insertions, 671 deletions
diff --git a/camel/ChangeLog b/camel/ChangeLog index 29be96a79e..80300e69d9 100644 --- a/camel/ChangeLog +++ b/camel/ChangeLog @@ -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 @@ -162,6 +181,7 @@ 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): diff --git a/camel/camel-data-cache.c b/camel/camel-data-cache.c index 5fa7a40de4..c2b8ac8bd2 100644 --- a/camel/camel-data-cache.c +++ b/camel/camel-data-cache.c @@ -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); } diff --git a/camel/camel-sasl-login.c b/camel/camel-sasl-login.c index 02e78d5194..f6c3c9e5f8 100644 --- a/camel/camel-sasl-login.c +++ b/camel/camel-sasl-login.c @@ -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."), diff --git a/camel/camel-session.c b/camel/camel-session.c index bcd680299a..93e6feaf7c 100644 --- a/camel/camel-session.c +++ b/camel/camel-session.c @@ -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); diff --git a/camel/providers/pop3/Makefile.am b/camel/providers/pop3/Makefile.am index cde7baf25a..4ce92eac2d 100644 --- a/camel/providers/pop3/Makefile.am +++ b/camel/providers/pop3/Makefile.am @@ -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 index 0000000000..22d3ab5930 --- /dev/null +++ b/camel/providers/pop3/camel-pop3-engine.c @@ -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 index 0000000000..418bc5efb9 --- /dev/null +++ b/camel/providers/pop3/camel-pop3-engine.h @@ -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 */ diff --git a/camel/providers/pop3/camel-pop3-folder.c b/camel/providers/pop3/camel-pop3-folder.c index 5452144eab..777f81fe9b 100644 --- a/camel/providers/pop3/camel-pop3-folder.c +++ b/camel/providers/pop3/camel-pop3-folder.c @@ -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" @@ -33,35 +36,28 @@ #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; } diff --git a/camel/providers/pop3/camel-pop3-folder.h b/camel/providers/pop3/camel-pop3-folder.h index 0b6296a09c..55eb1d253b 100644 --- a/camel/providers/pop3/camel-pop3-folder.h +++ b/camel/providers/pop3/camel-pop3-folder.h @@ -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 */ diff --git a/camel/providers/pop3/camel-pop3-provider.c b/camel/providers/pop3/camel-pop3-provider.c index 9e7a022482..85ff1f0865 100644 --- a/camel/providers/pop3/camel-pop3-provider.c +++ b/camel/providers/pop3/camel-pop3-provider.c @@ -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); } diff --git a/camel/providers/pop3/camel-pop3-store.c b/camel/providers/pop3/camel-pop3-store.c index 6ff88d61c2..520d758661 100644 --- a/camel/providers/pop3/camel-pop3-store.c +++ b/camel/providers/pop3/camel-pop3-store.c @@ -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 @@ -38,23 +39,6 @@ #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; -} - diff --git a/camel/providers/pop3/camel-pop3-store.h b/camel/providers/pop3/camel-pop3-store.h index fcc488f3e8..8d560f8247 100644 --- a/camel/providers/pop3/camel-pop3-store.h +++ b/camel/providers/pop3/camel-pop3-store.h @@ -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 index 0000000000..5b0dd979ca --- /dev/null +++ b/camel/providers/pop3/camel-pop3-stream.c @@ -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 index 0000000000..2baf48d21d --- /dev/null +++ b/camel/providers/pop3/camel-pop3-stream.h @@ -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 */ |