Commit 014c99be 014c99be6f811a0e099f3c0013a0a0bcc6316a1c by Sergey Poznyakoff

Further improvements of IMAP LIST functionality

* imap4d/list.c (list_fun): Ignore names that contain delimiter
as part of their name (untranslatable names);
Don't insert spurious separators.
(list_ref): Make sure a pathnames are properly separated from
namespace prefixes.
(imap4d_list): Fix eventual use of uninitialized pfx.
* imap4d/namespace.c (prefix_translate_name): Allow for NS_OTHER
prefixes ending with a delimiter.
(extract_username): Return NULL if prefix without trailing delimiter
was used.
* imap4d/tests/atlocal.in (make_config): Add more namespace prefixes.
* imap4d/tests/list.at: Add more tests.
* libmailutils/string/expvar.c (exp_getvar): Return MU_WRDSE_UNDEF if
the variable is defined, but has NULL value.
1 parent 8282a0f6
......@@ -42,12 +42,17 @@ list_fun (mu_folder_t folder, struct mu_list_response *resp, void *data)
&& refinfo->pfx->record != resp->format)
return 0;
name = resp->name;
size = strlen (name);
if (size == refinfo->reflen + 6
&& memcmp (name + refinfo->reflen + 1, "INBOX", 5) == 0)
name = resp->name + refinfo->dirlen;
/* There can be only one INBOX */
if (refinfo->reflen == 0 && strcmp (name, "INBOX") == 0)
return 0;
/* Ignore mailboxes that contain delimiter as part of their name */
if (refinfo->pfx->delim != resp->separator
&& strchr (name, refinfo->pfx->delim))
return 0;
io_sendf ("* %s", "LIST (");
if ((resp->type & (MU_FOLDER_ATTRIBUTE_FILE|MU_FOLDER_ATTRIBUTE_DIRECTORY))
== (MU_FOLDER_ATTRIBUTE_FILE|MU_FOLDER_ATTRIBUTE_DIRECTORY))
......@@ -59,7 +64,6 @@ list_fun (mu_folder_t folder, struct mu_list_response *resp, void *data)
io_sendf (") \"%c\" ", refinfo->pfx->delim);
name = resp->name + refinfo->dirlen + 1;
size = strlen (name) + refinfo->reflen + 2;
if (size > refinfo->bufsize)
{
......@@ -77,9 +81,8 @@ list_fun (mu_folder_t folder, struct mu_list_response *resp, void *data)
if (refinfo->refptr[0])
{
p = mu_stpcpy (refinfo->buf, refinfo->refptr);
if (refinfo->reflen == strlen (refinfo->pfx->prefix) - 1)
*p++ = refinfo->pfx->delim;
memcpy (refinfo->buf, refinfo->refptr, refinfo->reflen);
p = refinfo->buf + refinfo->reflen;
}
else
p = refinfo->buf;
......@@ -101,6 +104,21 @@ list_fun (mu_folder_t folder, struct mu_list_response *resp, void *data)
}
static int
match_pfx (struct namespace_prefix const *pfx, char const *ref)
{
char const *p = pfx->prefix, *q = ref;
for (; *q; p++, q++)
{
if (*p == 0 || *p != *q)
return 0;
}
if (*p == pfx->delim)
p++;
return *p == 0;
}
static int
list_ref (char const *ref, char const *wcard, char const *cwd,
struct namespace_prefix const *pfx)
{
......@@ -110,8 +128,7 @@ list_ref (char const *ref, char const *wcard, char const *cwd,
char const *dir;
mu_url_t url;
if (pfx->ns == NS_OTHER && strcmp (ref, pfx->prefix) == 0
&& strpbrk (wcard, "*%"))
if (pfx->ns == NS_OTHER && match_pfx (pfx, ref) && strpbrk (wcard, "*%"))
{
/* [A] server MAY return NO to such a LIST command, requiring that a
user name be included with the Other Users' Namespace prefix
......@@ -128,18 +145,23 @@ list_ref (char const *ref, char const *wcard, char const *cwd,
memset (&refinfo, 0, sizeof refinfo);
refinfo.pfx = pfx;
/* Note: original reference would always coincide with the pfx->prefix,
if it weren't for the special handling of NS_OTHER namespace, where
the part between the prefix and the first delimiter is considered to
be a user name and is handled as part of the actual prefix. */
refinfo.refptr = ref;
refinfo.reflen = strlen (ref);
refinfo.pfx = pfx;
mu_folder_get_url (folder, &url);
mu_url_sget_path (url, &dir);
refinfo.dirlen = strlen (dir);
if (refinfo.refptr[refinfo.reflen-1] == pfx->delim)
refinfo.reflen--;
else if (strcmp (ref, pfx->prefix) == 0)
refinfo.dirlen++;
/* The special name INBOX is included in the output from LIST, if
INBOX is supported by this server for this user and if the
uppercase string "INBOX" matches the interpreted reference and
......@@ -236,8 +258,7 @@ imap4d_list (struct imap4d_session *session,
else
{
char *cwd = NULL;
size_t i;
struct namespace_prefix const *pfx;
struct namespace_prefix const *pfx = NULL;
if (ref[0] == 0)
{
......@@ -254,27 +275,37 @@ imap4d_list (struct imap4d_session *session,
}
else
ref = mu_strdup (ref);
/* Find the longest directory prefix */
i = strcspn (wcard, "%*");
if (wcard[i])
if (!pfx)
{
cwd = namespace_translate_name (ref, &pfx);
if (cwd)
free (cwd);
}
if (pfx)
{
while (i > 0 && wcard[i - 1] != pfx->delim)
i--;
/* Append it to the reference */
if (i)
/* Find the longest directory prefix */
size_t i = strcspn (wcard, "%*");
if (wcard[i])
{
size_t reflen = strlen (ref);
size_t len = i + reflen;
ref = mu_realloc (ref, len);
memcpy (ref + reflen, wcard, i - 1); /* omit the trailing / */
ref[len-1] = 0;
wcard += i;
while (i > 0 && wcard[i - 1] != pfx->delim)
i--;
/* Append it to the reference */
if (i)
{
size_t reflen = strlen (ref);
size_t len = i + reflen;
ref = mu_realloc (ref, len);
memcpy (ref + reflen, wcard, i - 1); /* omit the trailing / */
ref[len-1] = 0;
wcard += i;
}
}
}
cwd = namespace_translate_name (ref, &pfx);
if (cwd)
status = list_ref (ref, wcard, cwd, pfx);
......
......@@ -188,9 +188,11 @@ prefix_translate_name (struct namespace_prefix const *pfx, char const *name,
p = mu_stpcpy (p, pfx->dir);
if (*name)
{
if (pfx->ns == NS_OTHER
&& pfx->prefix[strlen(pfx->prefix) - 1] != pfx->delim)
if (pfx->ns == NS_OTHER)
{
if (pfx->prefix[strlen (pfx->prefix) - 1] == pfx->delim)
++name;
while (*name && *name != pfx->delim)
name++;
}
......@@ -246,10 +248,15 @@ translate_name (char const *name, struct namespace_prefix const **return_pfx)
static char *
extract_username (char const *name, struct namespace_prefix const *pfx)
{
char const *p = name + strlen (pfx->prefix);
char *end = strchr (p, pfx->delim);
char const *p;
char *end;
char *user;
size_t len;
if (strlen (name) < strlen (pfx->prefix))
return NULL;
p = name + strlen (pfx->prefix);
end = strchr (p, pfx->delim);
if (end)
len = end - p;
......
......@@ -22,8 +22,22 @@ namespace personal {
prefix "" {
directory "$HOMEDIR";
}
prefix "~/" {
directory "$HOMEDIR";
prefix "#archive:" {
directory "$CWD/archive";
delimiter ".";
}
prefix "archive." {
directory "$CWD/archive";
delimiter ".";
}
}
namespace other {
prefix "~" {
directory "$CWD/home/\$user";
}
prefix "other/" {
directory "$CWD/home/\$user";
}
}
......
......@@ -18,15 +18,15 @@ m4_pushdef([IMAP4D_HOMEDIR],[spool])
dnl LIST_CHECK([NAME],[KW],[ARG],[OUTPUT],[EXTRA-CODE],[filter,][expand]
m4_define([LIST_CHECK],[
AT_SETUP([$1])
AT_SETUP([m4_if([$1],,[list $3],[$1])])
AT_KEYWORDS([list $2])
IMAP4D_CHECK([
mkdir IMAP4D_HOMEDIR
m4_foreach([MAILBOX],[bigto.mbox,mbox1,mbox,relational.mbox,relational.mbox,
search.mbox,sieve.mbox,teaparty.mbox],[
MUT_MBCOPY($abs_top_srcdir/testsuite/spool/MAILBOX,IMAP4D_HOMEDIR)
])
for name in bigto relational search sieve teaparty
do
cp $abs_top_srcdir/testsuite/spool/$name.mbox IMAP4D_HOMEDIR/$name
done
$5
],
[1 LIST $3
......@@ -39,54 +39,246 @@ AT_CLEANUP
])
dnl ----------------------------------------------------------------------
LIST_CHECK([asterisk],[list00],
["~" "*"],
LIST_CHECK([],[list02],
["" "*"],
[dnl
* LIST (\NoInferiors) "/" ~/bigto.mbox
* LIST (\NoInferiors) "/" ~/mbox
* LIST (\NoInferiors) "/" ~/mbox1
* LIST (\NoInferiors) "/" ~/relational.mbox
* LIST (\NoInferiors) "/" ~/search.mbox
* LIST (\NoInferiors) "/" ~/sieve.mbox
* LIST (\NoInferiors) "/" ~/teaparty.mbox
* LIST (\NoInferiors) "/" bigto
* LIST (\NoInferiors) "/" relational
* LIST (\NoInferiors) "/" search
* LIST (\NoInferiors) "/" sieve
* LIST (\NoInferiors) "/" teaparty
* LIST (\NoInferiors) NIL INBOX
])
LIST_CHECK([percent],[list01],
["~" "%"],
LIST_CHECK([],[list02],
["" "%"],
[dnl
* LIST (\NoInferiors) "/" ~/bigto.mbox
* LIST (\NoInferiors) "/" ~/mbox
* LIST (\NoInferiors) "/" ~/mbox1
* LIST (\NoInferiors) "/" ~/relational.mbox
* LIST (\NoInferiors) "/" ~/search.mbox
* LIST (\NoInferiors) "/" ~/sieve.mbox
* LIST (\NoInferiors) "/" ~/teaparty.mbox
* LIST (\NoInferiors) "/" bigto
* LIST (\NoInferiors) "/" relational
* LIST (\NoInferiors) "/" search
* LIST (\NoInferiors) "/" sieve
* LIST (\NoInferiors) "/" teaparty
* LIST (\NoInferiors) NIL INBOX
])
LIST_CHECK([empty ref + asterisk],[list02],
["" "*"],
LIST_CHECK([duplicate INBOX],[list02],
["" "%"],
[dnl
* LIST (\NoInferiors) "/" bigto
* LIST (\NoInferiors) "/" relational
* LIST (\NoInferiors) "/" search
* LIST (\NoInferiors) "/" sieve
* LIST (\NoInferiors) "/" teaparty
* LIST (\NoInferiors) NIL INBOX
],
[cp $abs_top_srcdir/testsuite/spool/mbox IMAP4D_HOMEDIR/INBOX
])
LIST_CHECK([],[list07],
["" INBOX],
[dnl
* LIST (\NoInferiors) "/" bigto.mbox
* LIST (\NoInferiors) "/" mbox
* LIST (\NoInferiors) "/" mbox1
* LIST (\NoInferiors) "/" relational.mbox
* LIST (\NoInferiors) "/" search.mbox
* LIST (\NoInferiors) "/" sieve.mbox
* LIST (\NoInferiors) "/" teaparty.mbox
* LIST (\NoInferiors) NIL INBOX
])
LIST_CHECK([],[list08],
["" "search"],
[dnl
* LIST (\NoInferiors) "/" search
])
LIST_CHECK([],[],
["" "#archive:*"],
[* LIST (\NoInferiors) "." #archive:mbox
* LIST (\NoInferiors) "." #archive:old.mbox1
* LIST (\NoInferiors) "." #archive:old.very.saved
* LIST (\NoSelect) "." #archive:old
* LIST (\NoSelect) "." #archive:old.very
],
[mkdir archive archive/old archive/old/very
cp $abs_top_srcdir/testsuite/spool/mbox archive
cp $abs_top_srcdir/testsuite/spool/mbox1 archive/old
cp $abs_top_srcdir/testsuite/spool/mbox1 archive/old/very/saved
])
LIST_CHECK([],[],
["" "#archive:%"],
[* LIST (\NoInferiors) "." #archive:mbox
* LIST (\NoSelect) "." #archive:old
],
[mkdir archive archive/old archive/old/very
cp $abs_top_srcdir/testsuite/spool/mbox archive
cp $abs_top_srcdir/testsuite/spool/mbox1 archive/old
cp $abs_top_srcdir/testsuite/spool/mbox1 archive/old/very/saved
])
LIST_CHECK([],[],
["" "archive.*"],
[* LIST (\NoInferiors) "." archive.mbox
* LIST (\NoInferiors) "." archive.old.mbox1
* LIST (\NoInferiors) "." archive.old.very.saved
* LIST (\NoSelect) "." archive.old
* LIST (\NoSelect) "." archive.old.very
],
[mkdir archive archive/old archive/old/very
cp $abs_top_srcdir/testsuite/spool/mbox archive
cp $abs_top_srcdir/testsuite/spool/mbox1 archive/old
cp $abs_top_srcdir/testsuite/spool/mbox1 archive/old/very/saved
])
LIST_CHECK([],[],
["archive" "*"],
[* LIST (\NoInferiors) "." archive.mbox
* LIST (\NoInferiors) "." archive.old.mbox1
* LIST (\NoInferiors) "." archive.old.very.saved
* LIST (\NoSelect) "." archive.old
* LIST (\NoSelect) "." archive.old.very
],
[mkdir archive archive/old archive/old/very
cp $abs_top_srcdir/testsuite/spool/mbox archive
cp $abs_top_srcdir/testsuite/spool/mbox1 archive/old
cp $abs_top_srcdir/testsuite/spool/mbox1 archive/old/very/saved
])
LIST_CHECK([],[],
["archive." "*"],
[* LIST (\NoInferiors) "." archive.mbox
* LIST (\NoInferiors) "." archive.old.mbox1
* LIST (\NoInferiors) "." archive.old.very.saved
* LIST (\NoSelect) "." archive.old
* LIST (\NoSelect) "." archive.old.very
],
[mkdir archive archive/old archive/old/very
cp $abs_top_srcdir/testsuite/spool/mbox archive
cp $abs_top_srcdir/testsuite/spool/mbox1 archive/old
cp $abs_top_srcdir/testsuite/spool/mbox1 archive/old/very/saved
])
LIST_CHECK([],[],
["archive." "*"],
[* LIST (\NoInferiors) "." archive.mbox
* LIST (\NoInferiors) "." archive.old.mbox1
* LIST (\NoInferiors) "." archive.old.very.saved
* LIST (\NoSelect) "." archive.old
* LIST (\NoSelect) "." archive.old.very
],
[mkdir archive archive/old archive/old/very
cp $abs_top_srcdir/testsuite/spool/mbox archive
cp $abs_top_srcdir/testsuite/spool/mbox archive/mbox.1
cp $abs_top_srcdir/testsuite/spool/mbox1 archive/old
cp $abs_top_srcdir/testsuite/spool/mbox1 archive/old/very/saved
])
LIST_CHECK([],[],
["~" "*"],
[1 NO LIST The requested item could not be found
])
LIST_CHECK([],[],
["~foo" "%"],
[* LIST (\NoInferiors) "/" ~foo/mbox
* LIST (\NoSelect) "/" ~foo/dir
],
[mkdir home home/foo home/foo/dir home/bar
cp $abs_top_srcdir/testsuite/spool/mbox home/foo
cp $abs_top_srcdir/testsuite/spool/mbox1 home/foo/dir/saved
cp $abs_top_srcdir/testsuite/spool/mbox1 home/bar
])
LIST_CHECK([],[],
["~foo" "*"],
[* LIST (\NoInferiors) "/" ~foo/dir/saved
* LIST (\NoInferiors) "/" ~foo/mbox
* LIST (\NoSelect) "/" ~foo/dir
],
[mkdir home home/foo home/foo/dir home/bar
cp $abs_top_srcdir/testsuite/spool/mbox home/foo
cp $abs_top_srcdir/testsuite/spool/mbox1 home/foo/dir/saved
cp $abs_top_srcdir/testsuite/spool/mbox1 home/bar
])
LIST_CHECK([],[],
["" "~foo/*"],
[* LIST (\NoInferiors) "/" ~foo/dir/saved
* LIST (\NoInferiors) "/" ~foo/mbox
* LIST (\NoSelect) "/" ~foo/dir
],
[mkdir home home/foo home/foo/dir home/bar
cp $abs_top_srcdir/testsuite/spool/mbox home/foo
cp $abs_top_srcdir/testsuite/spool/mbox1 home/foo/dir/saved
cp $abs_top_srcdir/testsuite/spool/mbox1 home/bar
])
LIST_CHECK([],[],
["~foo" "%/*"],
[* LIST (\NoInferiors) "/" ~foo/dir/saved
],
[mkdir home home/foo home/foo/dir home/bar
cp $abs_top_srcdir/testsuite/spool/mbox home/foo
cp $abs_top_srcdir/testsuite/spool/mbox1 home/foo/dir/saved
cp $abs_top_srcdir/testsuite/spool/mbox1 home/bar
])
# ###
LIST_CHECK([],[],
["other" "*"],
[1 NO LIST The requested item could not be found
])
LIST_CHECK([],[],
["other/foo" "%"],
[* LIST (\NoInferiors) "/" other/foo/mbox
* LIST (\NoSelect) "/" other/foo/dir
],
[mkdir home home/foo home/foo/dir home/bar
cp $abs_top_srcdir/testsuite/spool/mbox home/foo
cp $abs_top_srcdir/testsuite/spool/mbox1 home/foo/dir/saved
cp $abs_top_srcdir/testsuite/spool/mbox1 home/bar
])
LIST_CHECK([],[],
["other/foo" "*"],
[* LIST (\NoInferiors) "/" other/foo/dir/saved
* LIST (\NoInferiors) "/" other/foo/mbox
* LIST (\NoSelect) "/" other/foo/dir
],
[mkdir home home/foo home/foo/dir home/bar
cp $abs_top_srcdir/testsuite/spool/mbox home/foo
cp $abs_top_srcdir/testsuite/spool/mbox1 home/foo/dir/saved
cp $abs_top_srcdir/testsuite/spool/mbox1 home/bar
])
LIST_CHECK([],[],
["" "other/foo/*"],
[* LIST (\NoInferiors) "/" other/foo/dir/saved
* LIST (\NoInferiors) "/" other/foo/mbox
* LIST (\NoSelect) "/" other/foo/dir
],
[mkdir home home/foo home/foo/dir home/bar
cp $abs_top_srcdir/testsuite/spool/mbox home/foo
cp $abs_top_srcdir/testsuite/spool/mbox1 home/foo/dir/saved
cp $abs_top_srcdir/testsuite/spool/mbox1 home/bar
])
LIST_CHECK([],[],
["other/foo" "%/*"],
[* LIST (\NoInferiors) "/" other/foo/dir/saved
],
[mkdir home home/foo home/foo/dir home/bar
cp $abs_top_srcdir/testsuite/spool/mbox home/foo
cp $abs_top_srcdir/testsuite/spool/mbox1 home/foo/dir/saved
cp $abs_top_srcdir/testsuite/spool/mbox1 home/bar
])
# FIXME: I'm not sure whether it should include / in the reply.
LIST_CHECK([root ref + asterisk],[list03],
["/" "*"],
[dnl
* LIST (\NoInferiors) "/" /bigto.mbox
* LIST (\NoInferiors) "/" /mbox
* LIST (\NoInferiors) "/" /mbox1
* LIST (\NoInferiors) "/" /relational.mbox
* LIST (\NoInferiors) "/" /search.mbox
* LIST (\NoInferiors) "/" /sieve.mbox
* LIST (\NoInferiors) "/" /teaparty.mbox
* LIST (\NoInferiors) "/" /bigto
* LIST (\NoInferiors) "/" /relational
* LIST (\NoInferiors) "/" /search
* LIST (\NoInferiors) "/" /sieve
* LIST (\NoInferiors) "/" /teaparty
])
LIST_CHECK([absolute reference + asterisk],[list04],
......@@ -110,18 +302,6 @@ LIST_CHECK([absolute reference + mailbox],[list06],
MUT_MBCOPY($abs_top_srcdir/testsuite/folder/one,IMAP4D_HOMEDIR/folder)
MUT_MBCOPY($abs_top_srcdir/testsuite/folder/two,IMAP4D_HOMEDIR/folder)])
LIST_CHECK([empty reference + INBOX],[list07],
["" INBOX],
[dnl
* LIST (\NoInferiors) NIL INBOX
])
LIST_CHECK([empty reference + mailbox],[list08],
["" "search.mbox"],
[dnl
* LIST (\NoInferiors) "/" search.mbox
])
dnl ----------------------------------------------------------------------
m4_popdef([IMAP4D_HOMEDIR])
......
......@@ -77,7 +77,8 @@ exp_getvar (char **ret, const char *vptr, size_t vlen, void *data)
return MU_WRDSE_NOSPACE;
*ret = s;
}
else
rc = MU_WRDSE_UNDEF;
return rc;
}
......