changeset 754:b531f5cd415e HEAD

Rewrote imap_match() function. Maybe not as fast as before, but at least it's understandable now. This was required to fix listing mbox mailboxes where we wanted to match partial paths (it was pretty buggy before).
author Timo Sirainen <tss@iki.fi>
date Wed, 04 Dec 2002 00:44:38 +0200
parents 3521edb6c240
children 2330d6d75910
files AUTHORS COPYING src/imap/cmd-list.c src/lib-imap/imap-match.c src/lib-imap/imap-match.h src/lib-storage/index/maildir/maildir-list.c src/lib-storage/index/mbox/mbox-list.c src/lib-storage/subscription-file/subscription-file.c
diffstat 8 files changed, 258 insertions(+), 285 deletions(-) [+]
line wrap: on
line diff
--- a/AUTHORS	Tue Dec 03 15:55:44 2002 +0200
+++ b/AUTHORS	Wed Dec 04 00:44:38 2002 +0200
@@ -6,7 +6,7 @@
 
 This product includes software developed by Computing Services
 at Carnegie Mellon University (http://www.cmu.edu/computing/).
-(src/lib/base64.c, src/lib/mkgmtime.c, src/lib-imap/imap-match.c)
+(src/lib/base64.c, src/lib/mkgmtime.c)
 
 GLib Team (src/lib/hash.c, primes.c, strfuncs.c)
 ---------
--- a/COPYING	Tue Dec 03 15:55:44 2002 +0200
+++ b/COPYING	Wed Dec 04 00:44:38 2002 +0200
@@ -7,5 +7,3 @@
   - md5.c : Public Domain
   - base64.c, mkgmtime.c : BSD-like (read it)
   - hash.c, primes.c, strfuncs.c, tree.c : LGPL v2
-
-src/lib-imap/imap-match.c : BSD-like (read it)
--- a/src/imap/cmd-list.c	Tue Dec 03 15:55:44 2002 +0200
+++ b/src/imap/cmd-list.c	Wed Dec 04 00:44:38 2002 +0200
@@ -91,8 +91,7 @@
 }
 
 static void list_send(Client *client, ListNode *node, const char *cmd,
-		      const char *path, const char *sep,
-		      const ImapMatchGlob *glob)
+		      const char *path, const char *sep, ImapMatchGlob *glob)
 {
 	const char *name;
 
@@ -106,7 +105,7 @@
 			list_send(client, node->children, cmd, name, sep, glob);
 
 		if ((node->flags & MAILBOX_NOSELECT) &&
-		    imap_match(glob, name, 0, NULL) < 0) {
+		    imap_match(glob, name) <= 0) {
 			/* doesn't match the mask */
 			t_pop();
 			continue;
@@ -126,6 +125,7 @@
 	ListContext ctx;
 	const char *ref, *pattern;
 	char sep_chr, sep[3];
+	int failed;
 
 	sep_chr = client->storage->hierarchy_sep;
 	if (IS_ESCAPED_CHAR(sep_chr)) {
@@ -145,6 +145,7 @@
 		/* special request to return the hierarchy delimiter */
 		client_send_line(client, t_strconcat(
 			"* LIST (\\Noselect) \"", sep, "\" \"\"", NULL));
+		failed = FALSE;
 	} else {
 		if (*ref != '\0') {
 			/* join reference + pattern */
@@ -161,23 +162,30 @@
 		ctx.storage = client->storage;
 
 		if (!subscribed) {
-			client->storage->find_mailboxes(client->storage,
-							pattern,
-							list_func, &ctx);
+			failed = !client->storage->
+				find_mailboxes(client->storage,
+					       pattern, list_func, &ctx);
 		} else {
-			client->storage->find_subscribed(client->storage,
-							 pattern,
-							 list_func, &ctx);
+			failed = !client->storage->
+				find_subscribed(client->storage,
+						pattern, list_func, &ctx);
 		}
 
-		list_send(client, ctx.nodes, subscribed ? "LSUB" : "LIST",
-			  NULL, sep, imap_match_init(pattern, TRUE, sep_chr));
+		if (!failed) {
+			list_send(client, ctx.nodes,
+				  subscribed ? "LSUB" : "LIST", NULL, sep,
+				  imap_match_init(pattern, TRUE, sep_chr));
+		}
 		pool_unref(ctx.pool);
 	}
 
-	client_send_tagline(client, subscribed ?
-			    "OK Lsub completed." :
-			    "OK List completed.");
+	if (failed)
+		client_send_storage_error(client);
+	else {
+		client_send_tagline(client, subscribed ?
+				    "OK Lsub completed." :
+				    "OK List completed.");
+	}
 	return TRUE;
 }
 
--- a/src/lib-imap/imap-match.c	Tue Dec 03 15:55:44 2002 +0200
+++ b/src/lib-imap/imap-match.c	Wed Dec 04 00:44:38 2002 +0200
@@ -1,49 +1,8 @@
-/* Stripped down version of Cyrus imapd's glob.c
- *
- * Copyright (c) 1998-2000 Carnegie Mellon University.  All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *
- * 1. Redistributions of source code must retain the above copyright
- *    notice, this list of conditions and the following disclaimer. 
- *
- * 2. Redistributions in binary form must reproduce the above copyright
- *    notice, this list of conditions and the following disclaimer in
- *    the documentation and/or other materials provided with the
- *    distribution.
- *
- * 3. The name "Carnegie Mellon University" must not be used to
- *    endorse or promote products derived from this software without
- *    prior written permission. For permission or any other legal
- *    details, please contact  
- *      Office of Technology Transfer
- *      Carnegie Mellon University
- *      5000 Forbes Avenue
- *      Pittsburgh, PA  15213-3890
- *      (412) 268-4387, fax: (412) 268-7395
- *      tech-transfer@andrew.cmu.edu
- *
- * 4. Redistributions of any form whatsoever must retain the following
- *    acknowledgment:
- *    "This product includes software developed by Computing Services
- *     at Carnegie Mellon University (http://www.cmu.edu/computing/)."
- *
- * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
- * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
- * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE
- * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
- * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
- * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- *
- * Author: Chris Newman
- * Start Date: 4/5/93
- */
-/*
- * $Id$
- */
+/* Copyright (C) 2002 Timo Sirainen
+
+   imap_match_init() logic originates from Cyrus, but the code is fully
+   rewritten.
+*/
 
 #include "lib.h"
 #include "imap-match.h"
@@ -51,190 +10,180 @@
 #include <ctype.h>
 
 struct _ImapMatchGlob {
-    int inboxcase;
-    const char *gstar, *ghier, *gptr;	/* INBOX prefix comparison state */
-    char sep_char;		/* separator character */
-    char inbox[6];		/* INBOX in the correct case */
-    char str[1];		/* glob string */
+	int inboxcase;
+	const char *inboxcase_end;
+
+	char sep_char;
+	char mask[1];
 };
 
-/* name of "INBOX" -- must have no repeated substrings */
-static char inbox[] = "INBOX";
-#define INBOXLEN (sizeof (inbox) - 1)
+/* name of "INBOX" - must not have repeated substrings */
+static const char inbox[] = "INBOX";
+#define INBOXLEN (sizeof(inbox) - 1)
+
+ImapMatchGlob *imap_match_init(const char *str, int inboxcase, char separator)
+{
+	ImapMatchGlob *glob;
+	const char *p, *inboxp;
+	char *dst;
+
+	/* +1 from struct */
+	glob = t_malloc(sizeof(ImapMatchGlob) + strlen(str));
+	glob->sep_char = separator;
 
-/* initialize globbing structure
- *  This makes the following changes to the input string:
- *   1) '*' eats all '*'s and '%'s connected by any wildcard
- *   2) '%' eats all adjacent '%'s
- */
-const ImapMatchGlob *imap_match_init(const char *str, int inboxcase,
-				     char separator)
-{
-    ImapMatchGlob *g;
-    char *dst;
+	dst = glob->mask;
+	while (*str != '\0') {
+		if (*str == '*' || *str == '%') {
+			/* remove duplicate hierarchy wildcards */
+			while (*str == '%') str++;
 
-    g = t_malloc(sizeof(ImapMatchGlob) + strlen(str) + 1);
+			/* "%*" -> "*" */
+			if (*str == '*') {
+				/* remove duplicate wildcards */
+				while (*str == '*' || *str == '%')
+					str++;
+				*dst++ = '*';
+			} else {
+				*dst++ = '%';
+			}
+		} else {
+			*dst++ = *str++;
+		}
+	}
+	*dst++ = '\0';
 
-    strcpy(g->inbox, inbox);
-    g->sep_char = separator;
-    dst = g->str;
-    while (*str) {
-	if (*str == '*' || *str == '%') {
-	    /* remove duplicate hierarchy match (5) */
-	    while (*str == '%') ++str;
-	    /* If we found a '*', treat '%' as '*' (4) */
-	    if (*str == '*') {
-		/* remove duplicate wildcards (4) */
-		while (*str == '*' || (*str == '%' && str[1])) ++str;
-		*dst++ = '*';
-	    } else {
-		*dst++ = '%';
-	    }
-	} else {
-	    *dst++ = *str++;
-	}
-    }
-    *dst++ = '\0';
+	if (inboxcase) {
+		/* check if we could be comparing INBOX. */
+		inboxp = inbox;
+		glob->inboxcase = TRUE;
+		for (p = glob->mask; *p != '\0' && *p != '*'; p++) {
+			if (*p != '%') {
+				inboxp = strchr(inboxp, i_toupper(*p));
+				if (inboxp == NULL) {
+					glob->inboxcase = FALSE;
+					break;
+				}
 
-    /* pre-match "INBOX" to the pattern case insensitively and save state
-     * also keep track of the matching case for "INBOX"
-     * NOTE: this only works because "INBOX" has no repeated substrings
-     */
-    if (inboxcase) {
-	g->inboxcase = TRUE,
-	str = g->str;
-	dst = g->inbox;
-	g->gstar = g->ghier = NULL;
-	do {
-	    while (*dst && i_toupper(*str) == i_toupper(*dst)) {
-		*dst++ = *str++;
-	    }
-	    if (*str == '*') g->gstar = ++str, g->ghier = 0;
-	    else if (*str == '%') g->ghier = ++str;
-	    else break;
-	    if (*str != '%') {
-		while (*dst && i_toupper(*str) != i_toupper(*dst)) ++dst;
-	    }
-	} while (*str && *dst);
-	g->gptr = str;
-	if (*dst) g->inboxcase = FALSE;
-    }
+				if (*++inboxp == '\0') {
+					/* now check that it doesn't end with
+					   any invalid chars */
+					if (*++p == '%') p++;
+					if (*p != '\0' && *p != '*' &&
+					    *p != glob->sep_char)
+						glob->inboxcase = FALSE;
+					break;
+				}
+			}
+		}
 
-    return (g);
+		if (glob->inboxcase && inboxp != NULL && *inboxp != '\0' &&
+		    *p != '*' && (p != glob->mask && p[-1] == '%'))
+			glob->inboxcase = FALSE;
+	}
+
+	return glob;
+}
+
+static inline int cmp_chr(const ImapMatchGlob *glob,
+			  const char *data, char maskchr)
+{
+	return *data == maskchr ||
+		(glob->inboxcase_end != NULL && data < glob->inboxcase_end &&
+		 i_toupper(*data) == i_toupper(maskchr));
 }
 
-/* returns -1 if no match, otherwise length of match or partial-match
- *  g         pre-processed glob string
- *  ptr       string to perform glob on
- *  len       length of ptr string
- *  min       pointer to minimum length of a valid partial-match
- *            set to return value + 1 on partial match, otherwise -1
- *            if NULL, partial matches not allowed
- */
-int imap_match(const ImapMatchGlob *glob, const char *ptr,
-	       int len, int *min)
+static int match_sub(const ImapMatchGlob *glob, const char **data_p,
+		     const char **mask_p)
 {
-    const char *gptr, *pend;	/* glob pointer, end of ptr string */
-    const char *gstar, *pstar;	/* pointers for '*' patterns */
-    const char *ghier, *phier;	/* pointers for '%' patterns */
-    const char *start;		/* start of input string */
-
-    /* check for remaining partial matches */
-    if (min && *min < 0) return (-1);
-
-    /* get length */
-    if (!len) len = strlen(ptr);
+	const char *mask, *data;
+	int ret, best_ret;
 
-    /* initialize globbing */
-    gptr = glob->str;
-    start = ptr;
-    pend = ptr + len;
-    gstar = ghier = NULL;
-    phier = pstar = NULL;	/* initialize to eliminate warnings */
-
-    /* check for INBOX prefix */
-    if (glob->inboxcase && strncmp(ptr, inbox, INBOXLEN) == 0) {
-	pstar = phier = ptr += INBOXLEN;
-	gstar = glob->gstar;
-	ghier = glob->ghier;
-	gptr = glob->gptr;
-    }
+	data = *data_p; mask = *mask_p;
 
-    /* main globbing loops */
-    /* case sensitive version */
-
-    /* loop to manage wildcards */
-    do {
-	/* see if we match to the next '%' or '*' wildcard */
-	while (*gptr != '*' && *gptr != '%' && ptr != pend && *gptr == *ptr) {
-	    ++ptr, ++gptr;
-	}
-	if (*gptr == '\0' && ptr == pend) break;
-	if (*gptr == '*') {
-	    ghier = NULL;
-	    gstar = ++gptr;
-	    pstar = ptr;
+	while (*mask != '\0' && *mask != '*' && *mask != '%') {
+		if (!cmp_chr(glob, data, *mask)) {
+			return *data == '\0' && *mask == glob->sep_char ?
+				0 : -1;
+		}
+		data++; mask++;
 	}
-	if (*gptr == '%') {
-	    ghier = ++gptr;
-	    phier = ptr;
-	}
-	if (ghier) {
-	    /* look for a match with first char following '%',
-	     * stop at a sep_char unless we're doing "*%"
-	     */
-	    ptr = phier;
-	    while (ptr != pend && *ghier != *ptr
-		   && (*ptr != glob->sep_char ||
-		       (!*ghier && gstar && *gstar == '%' && min
-			&& ptr - start < *min))) {
-		++ptr;
-	    }
-	    if (ptr == pend) {
-		gptr = ghier;
-		break;
-	    }
-	    if (*ptr == glob->sep_char && *ptr != *ghier) {
-		if (!*ghier && min
-		    && *min < ptr - start && ptr != pend
-		    && *ptr == glob->sep_char
-		    ) {
-		    *min = gstar ? ptr - start + 1 : -1;
-		    return (ptr - start);
+
+        best_ret = -1;
+	while (*mask == '%') {
+		mask++;
+
+		if (*mask == '\0') {
+			while (*data != '\0' && *data != glob->sep_char)
+				data++;
+			break;
 		}
-		gptr = ghier;
-		ghier = NULL;
-	    } else {
-		phier = ++ptr;
-		gptr = ghier + 1;
-	    }
-	}
-	if (gstar && !ghier) {
-	    if (!*gstar) {
-		ptr = pend;
-		break;
-	    }
-	    /* look for a match with first char following '*' */
-	    while (pstar != pend && *gstar != *pstar) ++pstar;
-	    if (pstar == pend) {
-		gptr = gstar;
-		break;
-	    }
-	    ptr = ++pstar;
-	    gptr = gstar + 1;
-	}
-	if (*gptr == '\0' && min && *min < ptr - start && ptr != pend &&
-	    *ptr == glob->sep_char) {
-	    /* The pattern ended on a hierarchy separator
-	     * return a partial match */
-	    *min = ptr - start + 1;
-	    return ptr - start;
+
+		while (*data != '\0') {
+			if (cmp_chr(glob, data, *mask)) {
+				ret = match_sub(glob, &data, &mask);
+				if (ret > 0)
+					break;
+
+				if (ret == 0)
+					best_ret = 0;
+			}
+
+			if (*data == glob->sep_char)
+				break;
+
+			data++;
+		}
 	}
 
-	/* continue if at wildcard or we passed an asterisk */
-    } while (*gptr == '*' || *gptr == '%' ||
-	     ((gstar || ghier) && (*gptr || ptr != pend)));
+	if (*mask != '*') {
+		if (*data == '\0' && *mask != '\0')
+			return *mask == glob->sep_char ? 0 : best_ret;
+
+		if (*data != '\0')
+			return best_ret;
+	}
+
+	*data_p = data;
+	*mask_p = mask;
+	return 1;
+}
+
+int imap_match(ImapMatchGlob *glob, const char *data)
+{
+	const char *mask;
+	int ret;
+
+	if (glob->inboxcase &&
+	    strncasecmp(data, inbox, INBOXLEN) == 0 &&
+	    (data[INBOXLEN] == '\0' || data[INBOXLEN] == glob->sep_char))
+		glob->inboxcase_end = data + INBOXLEN;
+	else
+		glob->inboxcase_end = NULL;
 
-    if (min) *min = -1;
-    return (*gptr == '\0' && ptr == pend ? ptr - start : -1);
+	mask = glob->mask;
+	if (*mask != '*') {
+		if ((ret = match_sub(glob, &data, &mask)) <= 0)
+			return ret;
+
+		if (*mask == '\0')
+			return 1;
+	}
+
+	while (*mask == '*') {
+		mask++;
+
+		if (*mask == '\0')
+			return 1;
+
+		while (*data != '\0') {
+			if (cmp_chr(glob, data, *mask)) {
+				if (match_sub(glob, &data, &mask) > 0)
+					break;
+			}
+
+			data++;
+		}
+	}
+
+	return *data == '\0' && *mask == '\0' ? 1 : 0;
 }
--- a/src/lib-imap/imap-match.h	Tue Dec 03 15:55:44 2002 +0200
+++ b/src/lib-imap/imap-match.h	Wed Dec 04 00:44:38 2002 +0200
@@ -5,18 +5,10 @@
 
 /* If inboxcase is TRUE, the "INBOX" string at the beginning of line is
    compared case-insensitively */
-const ImapMatchGlob *imap_match_init(const char *str, int inboxcase,
-				     char separator);
+ImapMatchGlob *imap_match_init(const char *str, int inboxcase, char separator);
 
-/* returns -1 if no match, otherwise length of match or partial-match
- *  glob      pre-processed glob string
- *  ptr       string to perform glob on
- *  len       length of ptr string (if 0, strlen() is used)
- *  min       pointer to minimum length of a valid partial-match.
- *            Set to -1 if no more matches.  Set to return value + 1
- *     	      if another match is possible.  If NULL, no partial-matches
- *            are returned.
- */
-int imap_match(const ImapMatchGlob *glob, const char *ptr, int len, int *min);
+/* Returns 1 if matched, 0 if it didn't match, but could match with additional
+   hierarchies, -1 if definitely didn't match */
+int imap_match(ImapMatchGlob *glob, const char *data);
 
 #endif
--- a/src/lib-storage/index/maildir/maildir-list.c	Tue Dec 03 15:55:44 2002 +0200
+++ b/src/lib-storage/index/maildir/maildir-list.c	Wed Dec 04 00:44:38 2002 +0200
@@ -74,7 +74,7 @@
 int maildir_find_mailboxes(MailStorage *storage, const char *mask,
 			   MailboxFunc func, void *context)
 {
-        const ImapMatchGlob *glob;
+        ImapMatchGlob *glob;
 	DIR *dirp;
 	struct dirent *d;
 	struct stat st;
@@ -106,7 +106,7 @@
 
 		/* make sure the mask matches - dirs beginning with ".."
 		   should be deleted and we always want to check those. */
-		if (fname[1] == '.' || imap_match(glob, fname+1, 0, NULL) < 0)
+		if (fname[1] == '.' || imap_match(glob, fname+1) <= 0)
 			continue;
 
 		/* make sure it's a directory */
@@ -142,8 +142,7 @@
 		func(storage, fname+1, flags, context);
 	}
 
-	if (!failed && !found_inbox &&
-	    imap_match(glob, "INBOX", 0, NULL) >= 0) {
+	if (!failed && !found_inbox && imap_match(glob, "INBOX") > 0) {
 		/* .INBOX directory doesn't exist yet, but INBOX still exists */
 		func(storage, "INBOX", 0, context);
 	}
--- a/src/lib-storage/index/mbox/mbox-list.c	Tue Dec 03 15:55:44 2002 +0200
+++ b/src/lib-storage/index/mbox/mbox-list.c	Wed Dec 04 00:44:38 2002 +0200
@@ -15,16 +15,19 @@
 	void *context;
 } FindSubscribedContext;
 
-static int mbox_find_path(MailStorage *storage, const ImapMatchGlob *glob,
+static int mbox_find_path(MailStorage *storage, ImapMatchGlob *glob,
 			  MailboxFunc func, void *context,
 			  const char *relative_dir, int *found_inbox)
 {
 	DIR *dirp;
 	struct dirent *d;
 	struct stat st;
-	const char *dir;
-	char fulldir[1024], path[1024];
-	int failed, len;
+	const char *dir, *listpath;
+	char fulldir[1024], path[1024], fullpath[1024];
+	int failed, match;
+	size_t len;
+
+	t_push();
 
 	if (relative_dir == NULL)
 		dir = storage->dir;
@@ -36,8 +39,11 @@
 
 	dirp = opendir(dir);
 	if (dirp == NULL) {
-		mail_storage_set_critical(storage, "opendir(%s) failed: %m",
-					  dir);
+		if (errno != ENOENT && errno != ENOTDIR) {
+			mail_storage_set_critical(storage,
+				"opendir(%s) failed: %m", dir);
+		}
+		t_pop();
 		return FALSE;
 	}
 
@@ -54,74 +60,96 @@
 		if (len > 5 && strcmp(fname+len-5, ".lock") == 0)
 			continue;
 
-		/* make sure the mask matches */
-		if (relative_dir == NULL) {
-			if (imap_match(glob, fname, 0, NULL) < 0)
-				continue;
-		} else {
-			i_snprintf(path, sizeof(path),
-				   "%s/%s", relative_dir, fname);
-			if (imap_match(glob, path, 0, NULL) < 0)
-				continue;
+		/* check the mask */
+		if (relative_dir == NULL)
+			listpath = fname;
+		else {
+			i_snprintf(path, sizeof(path), "%s/%s",
+				   relative_dir, fname);
+			listpath = path;
 		}
 
+		if ((match = imap_match(glob, listpath)) < 0)
+			continue;
+
 		/* see if it's a directory */
-		i_snprintf(path, sizeof(path), "%s/%s", dir, fname);
-		if (stat(path, &st) != 0) {
+		i_snprintf(fullpath, sizeof(fullpath), "%s/%s", dir, fname);
+		if (stat(fullpath, &st) != 0) {
 			if (errno == ENOENT)
 				continue; /* just deleted, ignore */
 
 			mail_storage_set_critical(storage, "stat(%s) failed: "
-						  "%m", path);
+						  "%m", fullpath);
 			failed = TRUE;
 			break;
 		}
 
-		if (relative_dir == NULL) {
-			strncpy(path, fname, sizeof(path)-1);
-			path[sizeof(path)-1] = '\0';
-		} else {
-			i_snprintf(path, sizeof(path), "%s/%s",
-				   relative_dir, fname);
-		}
-
 		if (S_ISDIR(st.st_mode)) {
 			/* subdirectory, scan it too */
+			func(storage, listpath, MAILBOX_NOSELECT, context);
+
 			if (!mbox_find_path(storage, glob, func,
-					    context, path, NULL)) {
+					    context, listpath, NULL)) {
 				failed = TRUE;
 				break;
 			}
-		} else {
+		} else if (match > 0) {
 			if (found_inbox != NULL &&
-			    strcasecmp(path, "inbox") == 0)
+			    strcasecmp(listpath, "inbox") == 0)
 				*found_inbox = TRUE;
 
-			func(storage, path, MAILBOX_NOINFERIORS, context);
+			func(storage, listpath, MAILBOX_NOINFERIORS, context);
 		}
 	}
 
+	t_pop();
+
 	(void)closedir(dirp);
 	return !failed;
 }
 
+static const char *mask_get_dir(const char *mask)
+{
+	const char *p, *last_dir;
+
+	last_dir = NULL;
+	for (p = mask; *p != '\0' && *p != '%' && *p != '*'; p++) {
+		if (*p == '/')
+			last_dir = p;
+	}
+
+	return last_dir != NULL ? t_strdup_until(mask, last_dir) : NULL;
+}
+
 int mbox_find_mailboxes(MailStorage *storage, const char *mask,
 			MailboxFunc func, void *context)
 {
-        const ImapMatchGlob *glob;
+	ImapMatchGlob *glob;
+	const char *relative_dir;
 	int found_inbox;
 
+	/* check that we're not trying to do any "../../" lists */
+	if (!mbox_is_valid_mask(mask)) {
+		mail_storage_set_error(storage, "Invalid mask");
+		return FALSE;
+	}
+
 	mail_storage_clear_error(storage);
 
+	/* if we're matching only subdirectories, don't bother scanning the
+	   parent directories */
+	relative_dir = mask_get_dir(mask);
+
 	glob = imap_match_init(mask, TRUE, '/');
 
 	found_inbox = FALSE;
 	if (!mbox_find_path(storage, glob, func, context,
-			    NULL, &found_inbox))
+			    relative_dir, &found_inbox))
 		return FALSE;
 
-	if (!found_inbox && imap_match(glob, "INBOX", 0, NULL) < 0) {
-		/* INBOX always exists */
+	if (!found_inbox && relative_dir == NULL &&
+	    imap_match(glob, "INBOX") > 0) {
+		/* INBOX always exists, even if the file doesn't. */
 		func(storage, "INBOX", MAILBOX_UNMARKED | MAILBOX_NOINFERIORS,
 		     context);
 	}
--- a/src/lib-storage/subscription-file/subscription-file.c	Tue Dec 03 15:55:44 2002 +0200
+++ b/src/lib-storage/subscription-file/subscription-file.c	Wed Dec 04 00:44:38 2002 +0200
@@ -166,7 +166,7 @@
 int subsfile_foreach(MailStorage *storage, const char *mask,
 		     SubsFileForeachFunc func, void *context)
 {
-        const ImapMatchGlob *glob;
+        ImapMatchGlob *glob;
 	const char *path, *start, *end, *p, *line;
 	void *mmap_base;
 	size_t mmap_length;
@@ -188,8 +188,7 @@
 		}
 
 		line = t_strdup_until(start, p);
-		if (line != NULL && *line != '\0' &&
-		    imap_match(glob, line, 0, NULL) >= 0)
+		if (line != NULL && *line != '\0' && imap_match(glob, line) > 0)
 			ret = func(storage, line, context);
 		t_pop();