Commit 35718326 35718326403f83b3e44cbeb8a3899e9d0d4dab90 by Sergey Poznyakoff

Implement general-purpose processing of unsolicited responses in imap client.

* include/mailutils/imap.h (mu_imap_response_action_t): New typedef.
(mu_imap_foreach_response): New proto.
(MU_IMAP_CB_PERMANENT_FLAGS,MU_IMAP_CB_MESSAGE_COUNT)
(MU_IMAP_CB_RECENT_COUNT,MU_IMAP_CB_FIRST_UNSEEN)
(MU_IMAP_CB_UIDNEXT,MU_IMAP_CB_UIDVALIDITY)
(MU_IMAP_CB_OK,_MU_IMAP_CB_MAX): New constants.
(mu_imap_callback_t): New typedef.
(mu_imap_callback,mu_imap_register_callback_function): New protos.
(MU_IMAP_RESPONSE_ALERT,MU_IMAP_RESPONSE_BADCHARSET)
(MU_IMAP_RESPONSE_CAPABILITY,MU_IMAP_RESPONSE_PARSE)
(MU_IMAP_RESPONSE_PERMANENTFLAGS,MU_IMAP_RESPONSE_READ_ONLY)
(MU_IMAP_RESPONSE_READ_WRITE,MU_IMAP_RESPONSE_TRYCREATE)
(MU_IMAP_RESPONSE_UIDNEXT,MU_IMAP_RESPONSE_UIDVALIDITY)
(MU_IMAP_RESPONSE_UNSEEN): New constants.
(mu_imap_response_codes): New extern.
* include/mailutils/sys/imap.h (_mu_imap) <callback>: New member.
(_mu_imap_collect_flags, _mu_imap_list_at): New protos.
* libproto/imap/resplist.c (_mu_imap_list_at): New function.
* libproto/imap/callback.c: New file.
* libproto/imap/resproc.c: New file.
* libproto/imap/Makefile.am: Add new files.
* libproto/imap/capability.c: Rewrite using mu_imap_foreach_response.
* libproto/imap/id.c: Likewise.
* libproto/imap/select.c: Likewise.
1 parent 530953e1
......@@ -18,6 +18,7 @@
#ifndef _MAILUTILS_IMAP_H
#define _MAILUTILS_IMAP_H
#include <stdarg.h>
#include <mailutils/iterator.h>
#include <mailutils/debug.h>
#include <mailutils/stream.h>
......@@ -99,6 +100,45 @@ int mu_imap_select (mu_imap_t imap, const char *mbox, int writable,
int mu_imap_status (mu_imap_t imap, const char *mbox, struct mu_imap_stat *ps);
extern struct mu_kwd _mu_imap_status_name_table[];
typedef void (*mu_imap_response_action_t) (mu_imap_t imap, mu_list_t resp,
void *data);
int mu_imap_foreach_response (mu_imap_t imap, mu_imap_response_action_t fun,
void *data);
#define MU_IMAP_CB_PERMANENT_FLAGS 0
#define MU_IMAP_CB_MESSAGE_COUNT 1
#define MU_IMAP_CB_RECENT_COUNT 2
#define MU_IMAP_CB_FIRST_UNSEEN 3
#define MU_IMAP_CB_UIDNEXT 4
#define MU_IMAP_CB_UIDVALIDITY 5
#define MU_IMAP_CB_OK 6
#define _MU_IMAP_CB_MAX 7
typedef void (*mu_imap_callback_t) (void *, int code, mu_list_t resp,
va_list ap);
void mu_imap_callback (mu_imap_t imap, int code, mu_list_t resp, ...);
void mu_imap_register_callback_function (mu_imap_t imap, int code,
mu_imap_callback_t callback,
void *data);
#define MU_IMAP_RESPONSE_ALERT 0
#define MU_IMAP_RESPONSE_BADCHARSET 1
#define MU_IMAP_RESPONSE_CAPABILITY 2
#define MU_IMAP_RESPONSE_PARSE 3
#define MU_IMAP_RESPONSE_PERMANENTFLAGS 4
#define MU_IMAP_RESPONSE_READ_ONLY 5
#define MU_IMAP_RESPONSE_READ_WRITE 6
#define MU_IMAP_RESPONSE_TRYCREATE 7
#define MU_IMAP_RESPONSE_UIDNEXT 8
#define MU_IMAP_RESPONSE_UIDVALIDITY 9
#define MU_IMAP_RESPONSE_UNSEEN 10
extern struct mu_kwd mu_imap_response_codes[];
#ifdef __cplusplus
}
......
......@@ -83,6 +83,13 @@ struct _mu_imap
char *mbox_name; /* Name of the currently opened mailbox */
int mbox_writable:1; /* Is it open read/write? */
struct mu_imap_stat mbox_stat; /* Stats obtained from it */
/* Callbacks */
struct
{
mu_imap_callback_t action;
void *data;
} callback[_MU_IMAP_CB_MAX];
};
enum imap_eltype
......@@ -160,6 +167,10 @@ int _mu_imap_untagged_response_add (mu_imap_t imap);
int _mu_imap_list_element_is_string (struct imap_list_element *elt,
const char *str);
int _mu_imap_collect_flags (struct imap_list_element *arg, int *res);
struct imap_list_element *_mu_imap_list_at (mu_list_t list, int idx);
# ifdef __cplusplus
}
......
......@@ -28,6 +28,7 @@ libmu_imap_la_LIBADD = ${MU_LIB_AUTH} ${MU_LIB_MAILUTILS} @INTLLIBS@
# url.c
libmu_imap_la_SOURCES = \
fake-folder.c\
callback.c\
capability.c\
capatst.c\
carrier.c\
......@@ -41,6 +42,7 @@ libmu_imap_la_SOURCES = \
logout.c\
resplist.c\
response.c\
resproc.c\
select.c\
state.c\
status.c\
......
/* Response processing for IMAP client.
GNU Mailutils -- a suite of utilities for electronic mail
Copyright (C) 2011 Free Software Foundation, Inc.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 3 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General
Public License along with this library. If not, see
<http://www.gnu.org/licenses/>. */
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <mailutils/errno.h>
#include <mailutils/sys/imap.h>
void
mu_imap_callback (mu_imap_t imap, int code, mu_list_t resp, ...)
{
va_list ap;
if (code < 0 || code > _MU_IMAP_CB_MAX || !imap->callback[code].action)
return;
va_start (ap, resp);
imap->callback[code].action (imap->callback[code].data, code, resp, ap);
va_end (ap);
}
void
mu_imap_register_callback_function (mu_imap_t imap, int code,
mu_imap_callback_t callback,
void *data)
{
if (code < 0 || code > _MU_IMAP_CB_MAX)
{
mu_error ("%s:%d: ignoring unsupported callback code %d",
__FILE__, __LINE__, code);
return;
}
imap->callback[code].action = callback;
imap->callback[code].data = data;
}
......@@ -54,17 +54,25 @@ _map_capa (void **itmv, size_t itmc, void *call_data)
return MU_LIST_MAP_STOP;
if (*n == 0)
{
++*n;
if (strcmp (elt->v.string, "CAPABILITY") == 0)
return MU_LIST_MAP_SKIP;
else
return MU_LIST_MAP_STOP;
*n = 1;
return MU_LIST_MAP_SKIP;
}
itmv[0] = elt->v.string;
elt->v.string = NULL;
return MU_LIST_MAP_OK;
}
static void
_capability_response_action (mu_imap_t imap, mu_list_t response, void *data)
{
struct imap_list_element *elt = _mu_imap_list_at (response, 0);
if (elt && _mu_imap_list_element_is_string (elt, "CAPABILITY"))
{
int n = 0;
mu_list_map (response, _map_capa, &n, 1, &imap->capa);
}
}
int
mu_imap_capability (mu_imap_t imap, int reread, mu_iterator_t *piter)
{
......@@ -113,22 +121,17 @@ mu_imap_capability (mu_imap_t imap, int reread, mu_iterator_t *piter)
return MU_ERR_REPLY;
else
{
size_t count;
struct imap_list_element *elt;
imap->state = MU_IMAP_CONNECTED;
mu_list_count (imap->untagged_resp, &count);
if (mu_list_get (imap->untagged_resp, 0, (void*)&elt) == 0)
status = mu_imap_foreach_response (imap,
_capability_response_action,
NULL);
if (status == 0)
{
/* Top-level elements are always of imap_eltype_list type. */
int n = 0;
status = mu_list_map (elt->v.list, _map_capa, &n, 1,
&imap->capa);
if (piter)
status = mu_list_get_iterator (imap->capa, piter);
else
status = 0;
}
if (piter)
status = mu_list_get_iterator (imap->capa, piter);
else
status = 0;
}
break;
......
......@@ -48,44 +48,25 @@ _id_mapper (void **itmv, size_t itmc, void *call_data)
return rc;
}
struct id_convert_state
static void
_id_response_action (mu_imap_t imap, mu_list_t response, void *data)
{
int item;
mu_assoc_t assoc;
int ret;
};
static int
_id_convert (void *item, void *data)
{
struct imap_list_element *elt = item;
struct id_convert_state *stp = data;
mu_assoc_t assoc = data;
struct imap_list_element *elt;
switch (stp->item)
elt = _mu_imap_list_at (response, 0);
if (elt && _mu_imap_list_element_is_string (elt, "ID"))
{
case 0:
if (!(elt->type == imap_eltype_string &&
strcmp (elt->v.string, "ID") == 0))
{
stp->ret = MU_ERR_PARSE;
return 1;
}
stp->item++;
return 0;
case 1:
elt = _mu_imap_list_at (response, 1);
if (elt->type == imap_eltype_list)
mu_list_gmap (elt->v.list, _id_mapper, 2, stp->assoc);
mu_list_gmap (elt->v.list, _id_mapper, 2, assoc);
}
return 1;
}
}
static int
parse_id_reply (mu_imap_t imap, mu_assoc_t *passoc)
{
int rc;
struct imap_list_element const *response;
struct id_convert_state st;
mu_assoc_t assoc;
rc = mu_assoc_create (&assoc, sizeof (char**), MU_ASSOC_ICASE);
......@@ -93,16 +74,11 @@ parse_id_reply (mu_imap_t imap, mu_assoc_t *passoc)
return rc;
mu_assoc_set_free (assoc, _id_free);
rc = mu_list_get (imap->untagged_resp, 0, (void*) &response);
rc = mu_imap_foreach_response (imap, _id_response_action, assoc);
if (rc)
return rc;
*passoc = assoc;
if (rc == MU_ERR_NOENT)
return 0;
st.item = 0;
st.assoc = assoc;
st.ret = 0;
mu_list_foreach (response->v.list, _id_convert, &st);
return st.ret;
return 0;
}
int
......
......@@ -264,3 +264,19 @@ _mu_imap_list_element_is_string (struct imap_list_element *elt,
return strcmp (elt->v.string, str) == 0;
}
struct imap_list_element *
_mu_imap_list_at (mu_list_t list, int idx)
{
struct imap_list_element *arg;
int rc = mu_list_get (list, idx, (void*) &arg);
if (rc)
{
mu_debug (MU_DEBCAT_MAILBOX, MU_DEBUG_ERROR,
("%s:%d: cannot get list element: %s",
__FILE__, __LINE__, mu_strerror (rc)));
return NULL;
}
return arg;
}
......
/* Response processing for IMAP client.
GNU Mailutils -- a suite of utilities for electronic mail
Copyright (C) 2011 Free Software Foundation, Inc.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 3 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General
Public License along with this library. If not, see
<http://www.gnu.org/licenses/>. */
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <mailutils/cstr.h>
#include <mailutils/stream.h>
#include <mailutils/errno.h>
#include <mailutils/sys/imap.h>
struct mu_kwd mu_imap_response_codes[] = {
/* [ALERT] */
{ "ALERT", MU_IMAP_RESPONSE_ALERT },
/* [BADCHARSET (opt-list)] */
{ "BADCHARSET", MU_IMAP_RESPONSE_BADCHARSET },
/* [CAPABILITY (list)] */
{ "CAPABILITY", MU_IMAP_RESPONSE_CAPABILITY },
/* [PARSE] text */
{ "PARSE", MU_IMAP_RESPONSE_PARSE },
/* [PERMANENTFLAGS (list)] */
{ "PERMANENTFLAGS", MU_IMAP_RESPONSE_PERMANENTFLAGS },
/* [READ-ONLY] */
{ "READ-ONLY", MU_IMAP_RESPONSE_READ_ONLY },
/* [READ-WRITE] */
{ "READ-WRITE", MU_IMAP_RESPONSE_READ_WRITE },
/* [TRYCREATE] */
{ "TRYCREATE", MU_IMAP_RESPONSE_TRYCREATE },
/* [UIDNEXT N] */
{ "UIDNEXT", MU_IMAP_RESPONSE_UIDNEXT },
/* [UIDVALIDITY N] */
{ "UIDVALIDITY", MU_IMAP_RESPONSE_UIDVALIDITY },
/* [UNSEEN N] */
{ "UNSEEN", MU_IMAP_RESPONSE_UNSEEN },
{ NULL }
};
static void
ok_response (mu_imap_t imap, mu_list_t resp, void *data)
{
struct imap_list_element *arg;
int rcode = -1;
size_t n = 0;
arg = _mu_imap_list_at (resp, 1);
if (!arg)
return;
if (arg->type == imap_eltype_string && arg->v.string[0] == '[')
{
char *p;
size_t len = strcspn (arg->v.string, "]");
if (mu_kwd_xlat_name_len (mu_imap_response_codes,
arg->v.string + 1, len - 1, &rcode))
rcode = -1;
switch (rcode)
{
case MU_IMAP_RESPONSE_PERMANENTFLAGS:
arg = _mu_imap_list_at (resp, 2);
if (!arg ||
_mu_imap_collect_flags (arg, &imap->mbox_stat.permanent_flags))
break;
imap->mbox_stat.flags |= MU_IMAP_STAT_PERMANENT_FLAGS;
mu_imap_callback (imap, MU_IMAP_CB_PERMANENT_FLAGS, resp,
&imap->mbox_stat);
return;
case MU_IMAP_RESPONSE_UIDNEXT:
arg = _mu_imap_list_at (resp, 2);
if (!arg || arg->type != imap_eltype_string)
break;
n = strtoul (arg->v.string, &p, 10);
if (*p == ']')
{
imap->mbox_stat.uidnext = n;
imap->mbox_stat.flags |= MU_IMAP_STAT_UIDNEXT;
mu_imap_callback (imap, MU_IMAP_CB_UIDNEXT, resp,
&imap->mbox_stat);
}
return;
case MU_IMAP_RESPONSE_UIDVALIDITY:
arg = _mu_imap_list_at (resp, 2);
if (!arg || arg->type != imap_eltype_string)
break;
n = strtoul (arg->v.string, &p, 10);
if (*p == ']')
{
imap->mbox_stat.uidvalidity = n;
imap->mbox_stat.flags |= MU_IMAP_STAT_UIDVALIDITY;
mu_imap_callback (imap, MU_IMAP_CB_UIDVALIDITY, resp,
&imap->mbox_stat);
}
return;
case MU_IMAP_RESPONSE_UNSEEN:
arg = _mu_imap_list_at (resp, 2);
if (!arg || arg->type != imap_eltype_string)
break;
n = strtoul (arg->v.string, &p, 10);
if (*p == ']')
{
imap->mbox_stat.first_unseen = n;
imap->mbox_stat.flags |= MU_IMAP_STAT_FIRST_UNSEEN;
mu_imap_callback (imap, MU_IMAP_CB_FIRST_UNSEEN, resp,
&imap->mbox_stat);
}
return;
}
}
mu_imap_callback (imap, MU_IMAP_CB_OK, resp, rcode);
}
struct response_closure
{
mu_imap_t imap;
mu_imap_response_action_t fun;
void *data;
};
struct resptab
{
char *name;
mu_imap_response_action_t action;
};
static struct resptab resptab[] = {
{ "OK", ok_response },
{ "NO", },
{ "BAD", },
{ "PREAUTH", },
{ "BYE", },
{ NULL }
};
static int
_std_unsolicited_response (mu_imap_t imap, size_t count, mu_list_t resp)
{
struct resptab *rp;
struct imap_list_element *arg = _mu_imap_list_at (resp, 0);
if (!arg)
return 1;
if (arg->type == imap_eltype_string)
for (rp = resptab; rp->name; rp++)
{
if (mu_c_strcasecmp (rp->name, arg->v.string) == 0)
{
if (!rp->action)
mu_debug (MU_DEBCAT_MAILBOX, MU_DEBUG_TRACE9,
("%s:%d: ignoring %s response",
__FILE__, __LINE__, rp->name));
else
rp->action (imap, resp, NULL);
return 0;
}
}
return 1;
}
static int
_process_unsolicited_response (mu_imap_t imap, mu_list_t resp)
{
size_t count;
struct imap_list_element *arg;
if (mu_list_count (resp, &count))
return 1;
if (_std_unsolicited_response (imap, count, resp) == 0)
return 0;
if (count == 2)
{
size_t n;
char *p;
arg = _mu_imap_list_at (resp, 1);
if (!arg)
return 1;
if (_mu_imap_list_element_is_string (arg, "EXISTS"))
{
arg = _mu_imap_list_at (resp, 0);
if (!arg)
return 1;
n = strtoul (arg->v.string, &p, 10);
if (*p)
return 1;
imap->mbox_stat.message_count = n;
imap->mbox_stat.flags |= MU_IMAP_STAT_MESSAGE_COUNT;
mu_imap_callback (imap, MU_IMAP_CB_MESSAGE_COUNT, resp, n);
return 0;
}
else if (_mu_imap_list_element_is_string (arg, "RECENT"))
{
arg = _mu_imap_list_at (resp, 0);
if (!arg)
return 1;
n = strtoul (arg->v.string, &p, 10);
if (*p)
return 1;
imap->mbox_stat.recent_count = n;
imap->mbox_stat.flags |= MU_IMAP_STAT_RECENT_COUNT;
mu_imap_callback (imap, MU_IMAP_CB_RECENT_COUNT, resp, n);
return 0;
}
}
return 1;
}
static int
_process_response (void *item, void *data)
{
struct imap_list_element *elt = item;
struct response_closure *clos = data;
if (elt->type != imap_eltype_list)
{
mu_debug (MU_DEBCAT_MAILBOX, MU_DEBUG_ERROR,
("ignoring string response \"%s\"", elt->v.string));
}
else if (_process_unsolicited_response (clos->imap, elt->v.list))
clos->fun (clos->imap, elt->v.list, clos->data);
return 0;
}
int
mu_imap_foreach_response (mu_imap_t imap, mu_imap_response_action_t fun,
void *data)
{
struct response_closure clos;
clos.imap = imap;
clos.fun = fun;
clos.data = data;
return mu_list_foreach (imap->untagged_resp, _process_response, &clos);
}
......@@ -38,103 +38,28 @@ _collect_flags (void *item, void *data)
return 0;
}
static int
_parse_stat (void *item, void *data)
int
_mu_imap_collect_flags (struct imap_list_element *arg, int *res)
{
if (arg->type != imap_eltype_list)
return EINVAL;
mu_list_foreach (arg->v.list, _collect_flags, res);
return 0;
}
static void
_select_response_action (mu_imap_t imap, mu_list_t response, void *data)
{
struct imap_list_element *response = item;
mu_imap_t imap = data;
struct imap_list_element *elt;
size_t count;
int rc;
char *p;
if (response->type != imap_eltype_list)
return 0;
mu_list_count (response->v.list, &count);
rc = mu_list_get (response->v.list, 0, (void*) &elt);
if (rc)
return rc;
if (_mu_imap_list_element_is_string (elt, "OK"))
{
struct imap_list_element *arg;
if (count < 3)
return 0; /* ignore the line */
rc = mu_list_get (response->v.list, 1, (void*) &elt);
if (rc)
return rc;
rc = mu_list_get (response->v.list, 2, (void*) &arg);
if (rc)
return rc;
if (_mu_imap_list_element_is_string (elt, "[UIDVALIDITY"))
{
if (arg->type != imap_eltype_string)
return 0;
imap->mbox_stat.uidvalidity = strtoul (arg->v.string, &p, 10);
if (*p == ']')
imap->mbox_stat.flags |= MU_IMAP_STAT_UIDVALIDITY;
}
else if (_mu_imap_list_element_is_string (elt, "[UIDNEXT"))
{
if (arg->type != imap_eltype_string)
return 0;
imap->mbox_stat.uidnext = strtoul (arg->v.string, &p, 10);
if (*p == ']')
imap->mbox_stat.flags |= MU_IMAP_STAT_UIDNEXT;
}
else if (_mu_imap_list_element_is_string (elt, "[UNSEEN"))
{
if (arg->type != imap_eltype_string)
return 0;
imap->mbox_stat.first_unseen = strtoul (arg->v.string, &p, 10);
if (*p == ']')
imap->mbox_stat.flags |= MU_IMAP_STAT_FIRST_UNSEEN;
}
else if (_mu_imap_list_element_is_string (elt, "[PERMANENTFLAGS"))
{
if (arg->type != imap_eltype_list)
return 0;
mu_list_foreach (arg->v.list, _collect_flags,
&imap->mbox_stat.permanent_flags);
imap->mbox_stat.flags |= MU_IMAP_STAT_PERMANENT_FLAGS;
}
}
else if (_mu_imap_list_element_is_string (elt, "FLAGS"))
{
struct imap_list_element *arg;
rc = mu_list_get (response->v.list, 1, (void*) &arg);
if (rc)
return 0;
if (arg->type != imap_eltype_list)
return 0;
mu_list_foreach (arg->v.list, _collect_flags, &imap->mbox_stat.defined_flags);
imap->mbox_stat.flags |= MU_IMAP_STAT_DEFINED_FLAGS;
}
else if (count == 2)
elt = _mu_imap_list_at (response, 0);
if (elt && _mu_imap_list_element_is_string (elt, "FLAGS"))
{
struct imap_list_element *arg;
rc = mu_list_get (response->v.list, 1, (void*) &arg);
if (rc)
return rc;
if (_mu_imap_list_element_is_string (arg, "EXISTS"))
{
imap->mbox_stat.message_count = strtoul (elt->v.string, &p, 10);
if (*p == 0)
imap->mbox_stat.flags |= MU_IMAP_STAT_MESSAGE_COUNT;
}
else if (_mu_imap_list_element_is_string (arg, "RECENT"))
{
imap->mbox_stat.recent_count = strtoul (elt->v.string, &p, 10);
if (*p == 0)
imap->mbox_stat.flags |= MU_IMAP_STAT_RECENT_COUNT;
}
struct imap_list_element *arg = _mu_imap_list_at (response, 1);
if (arg &&
_mu_imap_collect_flags (arg, &imap->mbox_stat.defined_flags) == 0)
imap->mbox_stat.flags |= MU_IMAP_STAT_DEFINED_FLAGS;
}
return 0;
}
int
......@@ -202,7 +127,7 @@ mu_imap_select (mu_imap_t imap, const char *mbox, int writable,
}
imap->mbox_writable = writable;
memset (&imap->mbox_stat, 0, sizeof (imap->mbox_stat));
mu_list_foreach (imap->untagged_resp, _parse_stat, imap);
mu_imap_foreach_response (imap, _select_response_action, NULL);
if (ps)
*ps = imap->mbox_stat;
break;
......