/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
/* imap_match_init() logic originates from Cyrus, but the code is fully
rewritten. */
#include "lib.h"
#include "array.h"
#include "imap-match.h"
#include <ctype.h>
struct imap_match_pattern {
const char *pattern;
bool inboxcase;
};
struct imap_match_glob {
pool_t pool;
struct imap_match_pattern *patterns;
char sep;
char patterns_data[FLEXIBLE_ARRAY_MEMBER];
};
struct imap_match_context {
const char *inboxcase_end;
char sep;
bool inboxcase;
};
/* name of "INBOX" - must not have repeated substrings */
static const char inbox[] = "INBOX";
#define INBOXLEN (sizeof(inbox) - 1)
struct imap_match_glob *
imap_match_init(pool_t pool, const char *pattern,
bool inboxcase, char separator)
{
const char *patterns[2];
patterns[0] = pattern;
patterns[1] = NULL;
return imap_match_init_multiple(pool, patterns, inboxcase, separator);
}
static const char *pattern_compress(const char *pattern)
{
char *dest, *ret;
dest = ret = t_strdup_noconst(pattern);
/* @UNSAFE: compress the pattern */
while (*pattern != '\0') {
if (*pattern == '*' || *pattern == '%') {
/* remove duplicate hierarchy wildcards */
while (*pattern == '%') pattern++;
/* "%*" -> "*" */
if (*pattern == '*') {
/* remove duplicate wildcards */
while (*pattern == '*' || *pattern == '%')
pattern++;
*dest++ = '*';
} else {
*dest++ = '%';
}
} else {
*dest++ = *pattern++;
}
}
*dest = '\0';
return ret;
}
static bool pattern_is_inboxcase(const char *pattern, char separator)
{
const char *p = pattern, *inboxp = inbox;
/* skip over exact matches */
while (*inboxp == i_toupper(*p) && *p != '\0') {
inboxp++; p++;
}
if (*p != '%') {
return *p == '*' || *p == separator ||
(*inboxp == '\0' && *p == '\0');
}
/* handle 'I%B%X' style checks */
for (; *p != '\0' && *p != '*' && *p != separator; p++) {
if (*p != '%') {
inboxp = strchr(inboxp, i_toupper(*p));
if (inboxp == NULL)
return FALSE;
if (*++inboxp == '\0') {
/* now check that it doesn't end with
any invalid chars */
if (*++p == '%') p++;
if (*p != '\0' && *p != '*' &&
*p != separator)
return FALSE;
break;
}
}
}
return TRUE;
}
static struct imap_match_glob *
imap_match_init_multiple_real(pool_t pool, const char *const *patterns,
bool inboxcase, char separator)
{
struct imap_match_glob *glob;
struct imap_match_pattern *match_patterns;
unsigned int i, patterns_count;
size_t len, pos, patterns_data_len = 0;
patterns_count = str_array_length(patterns);
match_patterns = p_new(pool, struct imap_match_pattern,
patterns_count + 1);
/* compress the patterns */
for (i = 0; i < patterns_count; i++) {
match_patterns[i].pattern = pattern_compress(patterns[i]);
match_patterns[i].inboxcase = inboxcase &&
pattern_is_inboxcase(match_patterns[i].pattern,
separator);
patterns_data_len += strlen(match_patterns[i].pattern) + 1;
}
patterns_count = i;
/* now we know how much memory we need */
glob = p_malloc(pool, sizeof(struct imap_match_glob) +
patterns_data_len);
glob->pool = pool;
glob->sep = separator;
/* copy pattern strings to our allocated memory */
for (i = 0, pos = 0; i < patterns_count; i++) {
len = strlen(match_patterns[i].pattern) + 1;
i_assert(pos + len <= patterns_data_len);
/* @UNSAFE */
memcpy(glob->patterns_data + pos,
match_patterns[i].pattern, len);
match_patterns[i].pattern = glob->patterns_data + pos;
pos += len;
}
glob->patterns = match_patterns;
return glob;
}
struct imap_match_glob *
imap_match_init_multiple(pool_t pool, const char *const *patterns,
bool inboxcase, char separator)
{
struct imap_match_glob *glob;
if (pool->datastack_pool) {
return imap_match_init_multiple_real(pool, patterns,
inboxcase, separator);
}
T_BEGIN {
glob = imap_match_init_multiple_real(pool, patterns,
inboxcase, separator);
} T_END;
return glob;
}
void imap_match_deinit(struct imap_match_glob **glob)
{
if (glob == NULL || *glob == NULL)
return;
p_free((*glob)->pool, (*glob)->patterns);
p_free((*glob)->pool, *glob);
*glob = NULL;
}
static struct imap_match_glob *
imap_match_dup_real(pool_t pool, const struct imap_match_glob *glob)
{
ARRAY_TYPE(const_string) patterns;
const struct imap_match_pattern *p;
bool inboxcase = FALSE;
t_array_init(&patterns, 8);
for (p = glob->patterns; p->pattern != NULL; p++) {
if (p->inboxcase)
inboxcase = TRUE;
array_append(&patterns, &p->pattern, 1);
}
array_append_zero(&patterns);
return imap_match_init_multiple_real(pool, array_idx(&patterns, 0),
inboxcase, glob->sep);
}
struct imap_match_glob *
imap_match_dup(pool_t pool, const struct imap_match_glob *glob)
{
struct imap_match_glob *new_glob;
if (pool->datastack_pool) {
return imap_match_dup_real(pool, glob);
} else {
T_BEGIN {
new_glob = imap_match_dup_real(pool, glob);
} T_END;
return new_glob;
}
}
bool imap_match_globs_equal(const struct imap_match_glob *glob1,
const struct imap_match_glob *glob2)
{
const struct imap_match_pattern *p1 = glob1->patterns;
const struct imap_match_pattern *p2 = glob2->patterns;
if (glob1->sep != glob2->sep)
return FALSE;
for (; p1->pattern != NULL && p2->pattern != NULL; p1++, p2++) {
if (strcmp(p1->pattern, p2->pattern) != 0)
return FALSE;
if (p1->inboxcase != p2->inboxcase)
return FALSE;
}
return p1->pattern == p2->pattern;
}
#define CMP_CUR_CHR(ctx, data, pattern) \
(*(data) == *(pattern) || \
(i_toupper(*(data)) == i_toupper(*(pattern)) && \
(data) < (ctx)->inboxcase_end))
static enum imap_match_result
match_sub(struct imap_match_context *ctx, const char **data_p,
const char **pattern_p)
{
enum imap_match_result ret, match;
unsigned int i;
const char *data = *data_p, *pattern = *pattern_p;
/* match all non-wildcards */
i = 0;
while (pattern[i] != '\0' && pattern[i] != '*' && pattern[i] != '%') {
if (!CMP_CUR_CHR(ctx, data+i, pattern+i)) {
if (data[i] != '\0')
return IMAP_MATCH_NO;
if (pattern[i] == ctx->sep)
return IMAP_MATCH_CHILDREN;
if (i > 0 && pattern[i-1] == ctx->sep) {
/* data="foo/" pattern = "foo/bar/%" */
return IMAP_MATCH_CHILDREN;
}
return IMAP_MATCH_NO;
}
i++;
}
data += i;
pattern += i;
if (*data == '\0' && *data_p != data && data[-1] == ctx->sep &&
*pattern != '\0') {
/* data="/" pattern="/%..." */
match = IMAP_MATCH_CHILDREN;
} else {
match = IMAP_MATCH_NO;
}
while (*pattern == '%') {
pattern++;
if (*pattern == '\0') {
/* match, if this is the last hierarchy */
while (*data != '\0' && *data != ctx->sep)
data++;
break;
}
/* skip over this hierarchy */
while (*data != '\0') {
if (CMP_CUR_CHR(ctx, data, pattern)) {
ret = match_sub(ctx, &data, &pattern);
if (ret == IMAP_MATCH_YES)
break;
match |= ret;
}
if (*data == ctx->sep)
break;
data++;
}
}
if (*pattern != '*') {
if (*data == '\0' && *pattern != '\0') {
if (*pattern == ctx->sep)
match |= IMAP_MATCH_CHILDREN;
return match;
}
if (*data != '\0') {
if (*pattern == '\0' && *data == ctx->sep)
match |= IMAP_MATCH_PARENT;
return match;
}
}
*data_p = data;
*pattern_p = pattern;
return IMAP_MATCH_YES;
}
static enum imap_match_result
imap_match_pattern(struct imap_match_context *ctx,
const char *data, const char *pattern)
{
enum imap_match_result ret, match;
ctx->inboxcase_end = data;
if (ctx->inboxcase && strncasecmp(data, inbox, INBOXLEN) == 0 &&
(data[INBOXLEN] == '\0' || data[INBOXLEN] == ctx->sep)) {
/* data begins with INBOX/, use case-insensitive comparison
for it */
ctx->inboxcase_end += INBOXLEN;
}
if (*pattern != '*') {
/* handle the pattern up to the first '*' */
ret = match_sub(ctx, &data, &pattern);
if (ret != IMAP_MATCH_YES || *pattern == '\0')
return ret;
}
match = IMAP_MATCH_CHILDREN;
while (*pattern == '*') {
pattern++;
if (*pattern == '\0')
return IMAP_MATCH_YES;
while (*data != '\0') {
if (CMP_CUR_CHR(ctx, data, pattern)) {
ret = match_sub(ctx, &data, &pattern);
if (ret == IMAP_MATCH_YES)
break;
match |= ret;
}
data++;
}
}
return *data == '\0' && *pattern == '\0' ?
IMAP_MATCH_YES : match;
}
enum imap_match_result
imap_match(struct imap_match_glob *glob, const char *data)
{
struct imap_match_context ctx;
unsigned int i;
enum imap_match_result ret, match;
match = IMAP_MATCH_NO;
ctx.sep = glob->sep;
for (i = 0; glob->patterns[i].pattern != NULL; i++) {
ctx.inboxcase = glob->patterns[i].inboxcase;
ret = imap_match_pattern(&ctx, data, glob->patterns[i].pattern);
if (ret == IMAP_MATCH_YES)
return IMAP_MATCH_YES;
match |= ret;
}
return match;
}