Implement IMAP LIST-EXTENDED
This commit is contained in:
@@ -4,6 +4,7 @@ add_executable(bongoimap
|
||||
idle.c
|
||||
uidplus.c
|
||||
special-use.c
|
||||
list-extended.c
|
||||
copy.c
|
||||
event.c
|
||||
fetch.c
|
||||
@@ -63,4 +64,13 @@ if(BUILD_TESTING)
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
)
|
||||
add_test(NAME imap-special-use COMMAND imap-special-use-test)
|
||||
|
||||
add_executable(imap-list-extended-test
|
||||
tests/list-extended-test.c
|
||||
list-extended.c
|
||||
)
|
||||
target_include_directories(imap-list-extended-test PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
)
|
||||
add_test(NAME imap-list-extended COMMAND imap-list-extended-test)
|
||||
endif()
|
||||
|
||||
@@ -56,7 +56,7 @@ IMAPFormatCapabilities(char *buffer, size_t buffer_size,
|
||||
|
||||
if (!Append(buffer, buffer_size, &length,
|
||||
"* CAPABILITY IMAP4rev1 IMAP4 NAMESPACE IDLE UIDPLUS MOVE "
|
||||
"SPECIAL-USE CREATE-SPECIAL-USE XSENDER") ||
|
||||
"SPECIAL-USE CREATE-SPECIAL-USE LIST-EXTENDED XSENDER") ||
|
||||
(!authenticated && tls_active &&
|
||||
!Append(buffer, buffer_size, &length,
|
||||
" AUTH=PLAIN AUTH=LOGIN SASL-IR")) ||
|
||||
|
||||
+245
-4
@@ -47,6 +47,7 @@
|
||||
#include "idle.h"
|
||||
#include "uidplus.h"
|
||||
#include "special-use.h"
|
||||
#include "list-extended.h"
|
||||
|
||||
ImapGlobal Imap;
|
||||
|
||||
@@ -1797,6 +1798,247 @@ SendFolderInfo(Connection *conn, const char *command, FolderInformation *folder)
|
||||
return(STATUS_ABORT);
|
||||
}
|
||||
|
||||
#define IMAP_LIST_MAX_PATTERNS 16
|
||||
|
||||
typedef struct {
|
||||
unsigned int selectionOptions;
|
||||
unsigned int returnOptions;
|
||||
size_t patternCount;
|
||||
char patterns[IMAP_LIST_MAX_PATTERNS][STORE_MAX_COLLECTION * 2];
|
||||
} ExtendedListRequest;
|
||||
|
||||
static long
|
||||
ListAddPattern(ExtendedListRequest *request, const char *reference,
|
||||
const char *pattern)
|
||||
{
|
||||
int written;
|
||||
|
||||
if (request->patternCount == IMAP_LIST_MAX_PATTERNS)
|
||||
return STATUS_INVALID_ARGUMENT;
|
||||
if (!*reference) {
|
||||
written = snprintf(request->patterns[request->patternCount],
|
||||
sizeof(request->patterns[0]), "%s", pattern);
|
||||
} else if (!*pattern) {
|
||||
written = snprintf(request->patterns[request->patternCount],
|
||||
sizeof(request->patterns[0]), "%s", reference);
|
||||
} else {
|
||||
written = snprintf(request->patterns[request->patternCount],
|
||||
sizeof(request->patterns[0]), "%s/%s",
|
||||
reference, pattern);
|
||||
}
|
||||
if (written < 0 || (size_t)written >= sizeof(request->patterns[0]))
|
||||
return STATUS_PATH_TOO_LONG;
|
||||
request->patternCount++;
|
||||
return STATUS_CONTINUE;
|
||||
}
|
||||
|
||||
static long
|
||||
ParseExtendedListRequest(ImapSession *session, char *start,
|
||||
ExtendedListRequest *request)
|
||||
{
|
||||
FolderPath reference;
|
||||
FolderPath pattern;
|
||||
char *ptr;
|
||||
char *argument = NULL;
|
||||
long ccode;
|
||||
|
||||
memset(request, 0, sizeof(*request));
|
||||
ptr = FindNextArgument(start);
|
||||
if (!ptr) return STATUS_INVALID_ARGUMENT;
|
||||
|
||||
if (*ptr == '(') {
|
||||
ccode = GrabArgument(session, &ptr, &argument);
|
||||
if (ccode != STATUS_CONTINUE) return ccode;
|
||||
if (!IMAPListParseSelectionOptions(argument,
|
||||
&request->selectionOptions)) {
|
||||
MemFree(argument);
|
||||
return STATUS_INVALID_ARGUMENT;
|
||||
}
|
||||
MemFree(argument);
|
||||
argument = NULL;
|
||||
if (request->selectionOptions == 0) return STATUS_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
ccode = GetPathArgument(session, ptr, &ptr, &reference, TRUE);
|
||||
if (ccode != STATUS_CONTINUE) return ccode;
|
||||
|
||||
{
|
||||
char *patternStart = FindNextArgument(ptr);
|
||||
if (!patternStart) {
|
||||
FreePathArgument(&reference);
|
||||
return STATUS_INVALID_ARGUMENT;
|
||||
}
|
||||
if (*patternStart == '(') {
|
||||
char *patterns[IMAP_LIST_MAX_PATTERNS];
|
||||
size_t count;
|
||||
size_t i;
|
||||
|
||||
ptr = patternStart;
|
||||
ccode = GrabArgument(session, &ptr, &argument);
|
||||
if (ccode != STATUS_CONTINUE) {
|
||||
FreePathArgument(&reference);
|
||||
return ccode;
|
||||
}
|
||||
count = IMAPListSplitPatterns(argument, patterns,
|
||||
IMAP_LIST_MAX_PATTERNS);
|
||||
if (count == 0) ccode = STATUS_INVALID_ARGUMENT;
|
||||
for (i = 0; i < count && ccode == STATUS_CONTINUE; i++) {
|
||||
ccode = ListAddPattern(request, reference.name, patterns[i]);
|
||||
}
|
||||
MemFree(argument);
|
||||
argument = NULL;
|
||||
} else {
|
||||
ccode = GetPathArgument(session, ptr, &ptr, &pattern, TRUE);
|
||||
if (ccode == STATUS_CONTINUE) {
|
||||
ccode = ListAddPattern(request, reference.name, pattern.name);
|
||||
FreePathArgument(&pattern);
|
||||
}
|
||||
}
|
||||
}
|
||||
FreePathArgument(&reference);
|
||||
if (ccode != STATUS_CONTINUE) return ccode;
|
||||
|
||||
ptr = FindNextArgument(ptr);
|
||||
if (ptr) {
|
||||
ccode = GrabArgument(session, &ptr, &argument);
|
||||
if (ccode != STATUS_CONTINUE) return ccode;
|
||||
if (XplStrCaseCmp(argument, "RETURN") != 0) {
|
||||
MemFree(argument);
|
||||
return STATUS_INVALID_ARGUMENT;
|
||||
}
|
||||
MemFree(argument);
|
||||
argument = NULL;
|
||||
|
||||
ptr = FindNextArgument(ptr);
|
||||
if (!ptr || *ptr != '(') return STATUS_INVALID_ARGUMENT;
|
||||
ccode = GrabArgument(session, &ptr, &argument);
|
||||
if (ccode != STATUS_CONTINUE) return ccode;
|
||||
if (!IMAPListParseReturnOptions(argument, &request->returnOptions)) {
|
||||
MemFree(argument);
|
||||
return STATUS_INVALID_ARGUMENT;
|
||||
}
|
||||
MemFree(argument);
|
||||
if (request->returnOptions == 0) return STATUS_INVALID_ARGUMENT;
|
||||
if (FindNextArgument(ptr)) return STATUS_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
if (request->selectionOptions & IMAP_LIST_SELECT_SUBSCRIBED)
|
||||
request->returnOptions |= IMAP_LIST_RETURN_SUBSCRIBED;
|
||||
return STATUS_CONTINUE;
|
||||
}
|
||||
|
||||
static BOOL
|
||||
FolderMatchesBaseSelection(const FolderInformation *folder,
|
||||
unsigned int selectionOptions)
|
||||
{
|
||||
if (selectionOptions & IMAP_LIST_SELECT_REMOTE) return FALSE;
|
||||
if ((selectionOptions & IMAP_LIST_SELECT_SUBSCRIBED) &&
|
||||
(folder->flags & STORE_COLLECTION_FLAG_NON_SUBSCRIBED)) return FALSE;
|
||||
if ((selectionOptions & IMAP_LIST_SELECT_SPECIAL_USE) &&
|
||||
!(folder->flags & STORE_COLLECTION_FLAG_SPECIAL_USE_MASK)) return FALSE;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static BOOL
|
||||
FolderHasMatchingDescendant(ImapSession *session, FolderInformation *folder,
|
||||
unsigned int selectionOptions)
|
||||
{
|
||||
unsigned long i;
|
||||
size_t length = strlen(folder->name.utf7);
|
||||
|
||||
for (i = (unsigned long)(folder - session->folder.list) + 1;
|
||||
i < session->folder.count; i++) {
|
||||
FolderInformation *candidate = &session->folder.list[i];
|
||||
if (strncmp(candidate->name.utf7, folder->name.utf7, length) != 0 ||
|
||||
candidate->name.utf7[length] != '/') break;
|
||||
if (FolderMatchesBaseSelection(candidate, selectionOptions)) return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static BOOL
|
||||
FolderMatchesPatterns(const ExtendedListRequest *request, const char *name)
|
||||
{
|
||||
size_t i;
|
||||
for (i = 0; i < request->patternCount; i++) {
|
||||
if (IMAPListPatternMatches(request->patterns[i], name)) return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static long
|
||||
SendExtendedFolderInfo(ImapSession *session, FolderInformation *folder,
|
||||
unsigned int returnOptions)
|
||||
{
|
||||
char attributes[192];
|
||||
char specialUse[96];
|
||||
size_t length = 0;
|
||||
size_t specialUseLength;
|
||||
#define APPEND_LIST_ATTRIBUTE(value) do { \
|
||||
const char *attribute_ = (value); \
|
||||
size_t attribute_length_ = strlen(attribute_); \
|
||||
if (length + (length ? 1 : 0) + attribute_length_ >= sizeof(attributes)) \
|
||||
return STATUS_ABORT; \
|
||||
if (length) attributes[length++] = ' '; \
|
||||
memcpy(attributes + length, attribute_, attribute_length_ + 1); \
|
||||
length += attribute_length_; \
|
||||
} while (0)
|
||||
|
||||
attributes[0] = '\0';
|
||||
if (folder->flags & STORE_COLLECTION_FLAG_HIERARCHY_ONLY)
|
||||
APPEND_LIST_ATTRIBUTE("\\NoSelect");
|
||||
if ((returnOptions & IMAP_LIST_RETURN_SUBSCRIBED) &&
|
||||
!(folder->flags & STORE_COLLECTION_FLAG_NON_SUBSCRIBED))
|
||||
APPEND_LIST_ATTRIBUTE("\\Subscribed");
|
||||
if (returnOptions & IMAP_LIST_RETURN_CHILDREN) {
|
||||
APPEND_LIST_ATTRIBUTE(FolderHasSubFolders(session, folder) ?
|
||||
"\\HasChildren" : "\\HasNoChildren");
|
||||
}
|
||||
specialUseLength = IMAPSpecialUseFormat((uint32_t)folder->flags,
|
||||
specialUse, sizeof(specialUse));
|
||||
if (specialUseLength) APPEND_LIST_ATTRIBUTE(specialUse);
|
||||
|
||||
#undef APPEND_LIST_ATTRIBUTE
|
||||
if (ConnWriteF(session->client.conn, "* LIST (%s) \"/\" \"%s\"\r\n",
|
||||
attributes, folder->name.utf7) != -1)
|
||||
return STATUS_CONTINUE;
|
||||
return STATUS_ABORT;
|
||||
}
|
||||
|
||||
static long
|
||||
SendExtendedList(ImapSession *session, const ExtendedListRequest *request)
|
||||
{
|
||||
unsigned long i;
|
||||
|
||||
/* Preserve the base LIST delimiter query used by old clients. */
|
||||
if (request->patternCount == 1 && request->patterns[0][0] == '\0' &&
|
||||
request->selectionOptions == 0) {
|
||||
FolderInformation delimiter;
|
||||
memset(&delimiter, 0, sizeof(delimiter));
|
||||
delimiter.name.utf7 = "";
|
||||
delimiter.flags = STORE_COLLECTION_FLAG_HIERARCHY_ONLY;
|
||||
return SendFolderInfo(session->client.conn, "LIST", &delimiter);
|
||||
}
|
||||
|
||||
for (i = 0; i < session->folder.count; i++) {
|
||||
FolderInformation *folder = &session->folder.list[i];
|
||||
BOOL selected;
|
||||
|
||||
if (!FolderMatchesPatterns(request, folder->name.utf7)) continue;
|
||||
selected = FolderMatchesBaseSelection(folder,
|
||||
request->selectionOptions);
|
||||
if (!selected &&
|
||||
(request->selectionOptions & IMAP_LIST_SELECT_RECURSIVE)) {
|
||||
selected = FolderHasMatchingDescendant(session, folder,
|
||||
request->selectionOptions);
|
||||
}
|
||||
if (!selected) continue;
|
||||
if (SendExtendedFolderInfo(session, folder, request->returnOptions) !=
|
||||
STATUS_CONTINUE) return STATUS_ABORT;
|
||||
}
|
||||
return STATUS_CONTINUE;
|
||||
}
|
||||
|
||||
|
||||
__inline static int
|
||||
SendMatchingFolders(ImapSession *session, const char *command, char *pattern, const BOOL skipUnsubscribed, const BOOL skipSubFolders)
|
||||
@@ -1958,14 +2200,13 @@ ImapCommandList(void *param)
|
||||
ImapSession *session = (ImapSession *)param;
|
||||
|
||||
long ccode;
|
||||
char pattern[STORE_MAX_COLLECTION * 2];
|
||||
unsigned long patternLen = 0;
|
||||
ExtendedListRequest request;
|
||||
|
||||
if ((ccode = CheckState(session, STATE_AUTH)) == STATUS_CONTINUE) {
|
||||
if ((ccode = EventsSend(session, STORE_EVENT_ALL)) == STATUS_CONTINUE) {
|
||||
if ((ccode = GetSearchPattern(session, session->command.buffer + strlen("LIST"), pattern, sizeof(pattern), &patternLen)) == STATUS_CONTINUE) {
|
||||
if ((ccode = ParseExtendedListRequest(session, session->command.buffer + strlen("LIST"), &request)) == STATUS_CONTINUE) {
|
||||
if ((ccode = FolderListLoad(session)) == STATUS_CONTINUE) {
|
||||
if ((ccode = FolderSend(session, "LIST", pattern, patternLen, FALSE)) == STATUS_CONTINUE) {
|
||||
if ((ccode = SendExtendedList(session, &request)) == STATUS_CONTINUE) {
|
||||
return(SendOk(session, "LIST"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,185 @@
|
||||
/****************************************************************************
|
||||
* <Novell-copyright>
|
||||
* Copyright (c) 2001 Novell, Inc. All Rights Reserved.
|
||||
*
|
||||
* 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, contact Novell, Inc.
|
||||
*
|
||||
* To contact Novell about this file by physical or electronic mail, you
|
||||
* may find current contact information at www.novell.com.
|
||||
* </Novell-copyright>
|
||||
****************************************************************************/
|
||||
|
||||
#include "list-extended.h"
|
||||
|
||||
#include <ctype.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
typedef struct {
|
||||
const char *name;
|
||||
unsigned int value;
|
||||
} ListOption;
|
||||
|
||||
static int
|
||||
TokenEquals(const char *start, size_t length, const char *expected)
|
||||
{
|
||||
size_t i;
|
||||
if (strlen(expected) != length) return 0;
|
||||
for (i = 0; i < length; i++) {
|
||||
if (tolower((unsigned char)start[i]) !=
|
||||
tolower((unsigned char)expected[i])) return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int
|
||||
ParseOptions(const char *text, const ListOption *known, size_t known_count,
|
||||
unsigned int *options)
|
||||
{
|
||||
unsigned int result = 0;
|
||||
|
||||
if (!text || !options) return 0;
|
||||
for (;;) {
|
||||
const char *start;
|
||||
size_t i;
|
||||
|
||||
while (*text && isspace((unsigned char)*text)) text++;
|
||||
if (!*text) {
|
||||
*options = result;
|
||||
return 1;
|
||||
}
|
||||
start = text;
|
||||
while (*text && !isspace((unsigned char)*text)) text++;
|
||||
for (i = 0; i < known_count; i++) {
|
||||
if (TokenEquals(start, (size_t)(text - start), known[i].name)) {
|
||||
if (result & known[i].value) return 0;
|
||||
result |= known[i].value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (i == known_count) return 0;
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
IMAPListParseSelectionOptions(const char *text, unsigned int *options)
|
||||
{
|
||||
static const ListOption Known[] = {
|
||||
{ "SUBSCRIBED", IMAP_LIST_SELECT_SUBSCRIBED },
|
||||
{ "REMOTE", IMAP_LIST_SELECT_REMOTE },
|
||||
{ "RECURSIVEMATCH", IMAP_LIST_SELECT_RECURSIVE },
|
||||
{ "SPECIAL-USE", IMAP_LIST_SELECT_SPECIAL_USE }
|
||||
};
|
||||
unsigned int parsed;
|
||||
|
||||
if (!ParseOptions(text, Known, sizeof(Known) / sizeof(Known[0]), &parsed))
|
||||
return 0;
|
||||
if ((parsed & IMAP_LIST_SELECT_RECURSIVE) &&
|
||||
!(parsed & IMAP_LIST_SELECT_SUBSCRIBED)) return 0;
|
||||
*options = parsed;
|
||||
return 1;
|
||||
}
|
||||
|
||||
int
|
||||
IMAPListParseReturnOptions(const char *text, unsigned int *options)
|
||||
{
|
||||
static const ListOption Known[] = {
|
||||
{ "SUBSCRIBED", IMAP_LIST_RETURN_SUBSCRIBED },
|
||||
{ "CHILDREN", IMAP_LIST_RETURN_CHILDREN },
|
||||
{ "SPECIAL-USE", IMAP_LIST_RETURN_SPECIAL_USE }
|
||||
};
|
||||
return ParseOptions(text, Known, sizeof(Known) / sizeof(Known[0]), options);
|
||||
}
|
||||
|
||||
int
|
||||
IMAPListPatternMatches(const char *pattern, const char *mailbox)
|
||||
{
|
||||
size_t pattern_length;
|
||||
size_t mailbox_length;
|
||||
unsigned char *previous;
|
||||
unsigned char *current;
|
||||
size_t i;
|
||||
size_t j;
|
||||
int result;
|
||||
|
||||
if (!pattern || !mailbox) return 0;
|
||||
pattern_length = strlen(pattern);
|
||||
mailbox_length = strlen(mailbox);
|
||||
previous = calloc(mailbox_length + 1, 1);
|
||||
current = calloc(mailbox_length + 1, 1);
|
||||
if (!previous || !current) {
|
||||
free(previous);
|
||||
free(current);
|
||||
return 0;
|
||||
}
|
||||
previous[0] = 1;
|
||||
|
||||
for (i = 1; i <= pattern_length; i++) {
|
||||
unsigned char *swap;
|
||||
memset(current, 0, mailbox_length + 1);
|
||||
if (pattern[i - 1] == '*') current[0] = previous[0];
|
||||
for (j = 1; j <= mailbox_length; j++) {
|
||||
if (pattern[i - 1] == '*') {
|
||||
current[j] = previous[j] || current[j - 1];
|
||||
} else if (pattern[i - 1] == '%') {
|
||||
current[j] = previous[j] ||
|
||||
(mailbox[j - 1] != '/' && current[j - 1]);
|
||||
} else {
|
||||
current[j] = previous[j - 1] &&
|
||||
pattern[i - 1] == mailbox[j - 1];
|
||||
}
|
||||
}
|
||||
swap = previous;
|
||||
previous = current;
|
||||
current = swap;
|
||||
}
|
||||
result = previous[mailbox_length] != 0;
|
||||
free(previous);
|
||||
free(current);
|
||||
return result;
|
||||
}
|
||||
|
||||
size_t
|
||||
IMAPListSplitPatterns(char *text, char **patterns, size_t capacity)
|
||||
{
|
||||
size_t count = 0;
|
||||
|
||||
if (!text || !patterns || capacity == 0) return 0;
|
||||
while (*text) {
|
||||
char *start;
|
||||
char *write;
|
||||
|
||||
while (*text && isspace((unsigned char)*text)) text++;
|
||||
if (!*text) break;
|
||||
if (count == capacity) return 0;
|
||||
|
||||
if (*text == '"') {
|
||||
text++;
|
||||
start = write = text;
|
||||
while (*text && *text != '"') {
|
||||
if (*text == '\\' && text[1]) text++;
|
||||
*write++ = *text++;
|
||||
}
|
||||
if (*text != '"') return 0;
|
||||
text++;
|
||||
*write = '\0';
|
||||
if (*text && !isspace((unsigned char)*text)) return 0;
|
||||
} else {
|
||||
start = text;
|
||||
while (*text && !isspace((unsigned char)*text)) text++;
|
||||
if (*text) *text++ = '\0';
|
||||
}
|
||||
patterns[count++] = start;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/****************************************************************************
|
||||
* <Novell-copyright>
|
||||
* Copyright (c) 2001 Novell, Inc. All Rights Reserved.
|
||||
*
|
||||
* 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, contact Novell, Inc.
|
||||
*
|
||||
* To contact Novell about this file by physical or electronic mail, you
|
||||
* may find current contact information at www.novell.com.
|
||||
* </Novell-copyright>
|
||||
****************************************************************************/
|
||||
|
||||
#ifndef BONGO_IMAP_LIST_EXTENDED_H
|
||||
#define BONGO_IMAP_LIST_EXTENDED_H
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#define IMAP_LIST_SELECT_SUBSCRIBED (1u << 0)
|
||||
#define IMAP_LIST_SELECT_REMOTE (1u << 1)
|
||||
#define IMAP_LIST_SELECT_RECURSIVE (1u << 2)
|
||||
#define IMAP_LIST_SELECT_SPECIAL_USE (1u << 3)
|
||||
|
||||
#define IMAP_LIST_RETURN_SUBSCRIBED (1u << 0)
|
||||
#define IMAP_LIST_RETURN_CHILDREN (1u << 1)
|
||||
#define IMAP_LIST_RETURN_SPECIAL_USE (1u << 2)
|
||||
|
||||
int IMAPListParseSelectionOptions(const char *text, unsigned int *options);
|
||||
int IMAPListParseReturnOptions(const char *text, unsigned int *options);
|
||||
int IMAPListPatternMatches(const char *pattern, const char *mailbox);
|
||||
size_t IMAPListSplitPatterns(char *text, char **patterns, size_t capacity);
|
||||
|
||||
#endif
|
||||
@@ -39,6 +39,7 @@ main(void)
|
||||
assert(strstr(buffer, "MOVE"));
|
||||
assert(strstr(buffer, "SPECIAL-USE"));
|
||||
assert(strstr(buffer, "CREATE-SPECIAL-USE"));
|
||||
assert(strstr(buffer, "LIST-EXTENDED"));
|
||||
assert(strstr(buffer, "XSENDER"));
|
||||
assert(strstr(buffer, "LOGINDISABLED"));
|
||||
assert(!strstr(buffer, "STARTTLS"));
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
/****************************************************************************
|
||||
* <Novell-copyright>
|
||||
* Copyright (c) 2001 Novell, Inc. All Rights Reserved.
|
||||
*
|
||||
* 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, contact Novell, Inc.
|
||||
*
|
||||
* To contact Novell about this file by physical or electronic mail, you
|
||||
* may find current contact information at www.novell.com.
|
||||
* </Novell-copyright>
|
||||
****************************************************************************/
|
||||
|
||||
#include "list-extended.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
unsigned int options;
|
||||
char group[] = "\"INBOX/%\" Archive/* Plain";
|
||||
char *patterns[4];
|
||||
size_t count;
|
||||
|
||||
assert(IMAPListParseSelectionOptions("SUBSCRIBED RECURSIVEMATCH", &options));
|
||||
assert(options == (IMAP_LIST_SELECT_SUBSCRIBED |
|
||||
IMAP_LIST_SELECT_RECURSIVE));
|
||||
assert(IMAPListParseSelectionOptions("SPECIAL-USE", &options));
|
||||
assert(options == IMAP_LIST_SELECT_SPECIAL_USE);
|
||||
assert(!IMAPListParseSelectionOptions("RECURSIVEMATCH", &options));
|
||||
assert(!IMAPListParseSelectionOptions("UNKNOWN", &options));
|
||||
assert(IMAPListParseReturnOptions("CHILDREN SUBSCRIBED", &options));
|
||||
assert(options == (IMAP_LIST_RETURN_CHILDREN |
|
||||
IMAP_LIST_RETURN_SUBSCRIBED));
|
||||
|
||||
assert(IMAPListPatternMatches("*", "Archive/2026/July"));
|
||||
assert(IMAPListPatternMatches("Archive/%", "Archive/2026"));
|
||||
assert(!IMAPListPatternMatches("Archive/%", "Archive/2026/July"));
|
||||
assert(IMAPListPatternMatches("A*e/%", "Archive/2026"));
|
||||
assert(!IMAPListPatternMatches("INBOX", "INBOX/Child"));
|
||||
|
||||
count = IMAPListSplitPatterns(group, patterns, 4);
|
||||
assert(count == 3);
|
||||
assert(strcmp(patterns[0], "INBOX/%") == 0);
|
||||
assert(strcmp(patterns[1], "Archive/*") == 0);
|
||||
assert(strcmp(patterns[2], "Plain") == 0);
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user