From 6e6a5bc2df0c2cbcb170800c6b5660c2123a36e7 Mon Sep 17 00:00:00 2001 From: Mario Fetka Date: Sun, 19 Jul 2026 10:31:06 +0200 Subject: [PATCH] Restore store-backed IMAP header search --- src/agents/store/search.c | 127 +++++++++++++++----------- src/libs/streamio/CMakeLists.txt | 17 ++++ src/libs/streamio/tests/search-test.c | 73 +++++++++++++++ 3 files changed, 164 insertions(+), 53 deletions(-) create mode 100644 src/libs/streamio/tests/search-test.c diff --git a/src/agents/store/search.c b/src/agents/store/search.c index a307b08..55ad6df 100644 --- a/src/agents/store/search.c +++ b/src/agents/store/search.c @@ -62,14 +62,13 @@ SearchDocument(StoreClient *client, StoreObject *document, ccode = ConnWriteStr(client->conn, MSG4224CANTREAD); break; } - /* FIXME - if (BongoStreamSearchRfc822Header(ReadSourceFile, (void *)f, info->data.mail.headerSize, + if (BongoStreamSearchRfc822Header(ReadSourceFile, (void *)f, + document->size, NULL, query->query)) { - ccode = ReportHit(client->conn, info); + ccode = ReportHit(client->conn, document); break; } - */ if (query->type == STORE_SEARCH_HEADERS) { break; } @@ -132,33 +131,16 @@ search_body: } case STORE_SEARCH_HEADER: { - /* 'subject' and 'from' fields are special cases because they are already in utf-8 in the info structure */ - /* FIXME - if (info->data.mail.subject && XplStrCaseCmp(query->header, "subject") == 0) { - if (BongoStrCaseStr(info->data.mail.subject, query->query)) { - ccode = ReportHit(client->conn, info); - } - break; - } - - if (XplStrCaseCmp(query->header, "from") == 0) { - - if (info->data.mail.from && BongoStrCaseStr(info->data.mail.from, query->query)) { - ccode = ReportHit(client->conn, info); - } - break; - } - if (XplFSeek64(f, 0, SEEK_SET)) { ccode = ConnWriteStr(client->conn, MSG4224CANTREAD); break; } - if (BongoStreamSearchRfc822Header(ReadSourceFile, (void *)f, info->data.mail.headerSize, + if (BongoStreamSearchRfc822Header(ReadSourceFile, (void *)f, + document->size, query->header, query->query)) { - ccode = ReportHit(client->conn, info); + ccode = ReportHit(client->conn, document); } - */ break; } @@ -167,6 +149,52 @@ search_body: return(ccode); } +static int +LoadCollectionDocuments(StoreClient *client, uint64_t collectionGuid, + GArray *documents) +{ + static const char query[] = + "SELECT guid, collection_guid, imap_uid, filename, type, flags, size, " + "time_created, time_modified FROM storeobject " + "WHERE collection_guid=?1 ORDER BY imap_uid, guid;"; + MsgSQLStatement statement; + MsgSQLStatement *prepared; + int status; + + memset(&statement, 0, sizeof(statement)); + if (MsgSQLBeginTransaction(client->storedb)) return -1; + prepared = MsgSQLPrepare(client->storedb, query, &statement); + if (!prepared) goto abort; + MsgSQLBindInt64(prepared, 1, collectionGuid); + while ((status = MsgSQLResults(client->storedb, prepared)) > 0) { + StoreObject document; + + memset(&document, 0, sizeof(document)); + document.guid = MsgSQLResultInt64(prepared, 0); + document.collection_guid = MsgSQLResultInt64(prepared, 1); + document.imap_uid = MsgSQLResultInt(prepared, 2); + MsgSQLResultText(prepared, 3, document.filename, MAX_FILE_NAME); + document.filename[MAX_FILE_NAME] = '\0'; + document.type = MsgSQLResultInt(prepared, 4); + document.flags = MsgSQLResultInt(prepared, 5); + document.size = MsgSQLResultInt64(prepared, 6); + document.time_created = MsgSQLResultInt(prepared, 7); + document.time_modified = MsgSQLResultInt(prepared, 8); + document.version = 1; + if (!STORE_IS_FOLDER(document.type)) + g_array_append_val(documents, document); + } + if (status < 0) goto abort; + MsgSQLFinalize(&statement); + if (MsgSQLCommitTransaction(client->storedb)) return -1; + return 0; + +abort: + MsgSQLFinalize(&statement); + MsgSQLAbortTransaction(client->storedb); + return -1; +} + CCode @@ -209,39 +237,32 @@ StoreCommandSEARCH(StoreClient *client, uint64_t guid, StoreSearchInfo *query) } // we're searching a collection - so search each document in it - /* FIXME - stmt = DStoreListColl(client->handle, guid, -1, -1); - if (!stmt) { + GArray *documents = g_array_new(FALSE, FALSE, sizeof(StoreObject)); + if (!documents || LoadCollectionDocuments(client, guid, documents) != 0) { + if (documents) g_array_free(documents, TRUE); return ConnWriteStr(client->conn, MSG5005DBLIBERR); - goto finish; } - - while (-1 != ccode) { - switch (DStoreInfoStmtStep(client->handle, stmt, &child)) { - case 0: - ccode = ConnWriteStr(client->conn, MSG1000OK); - goto finish; - case -1: - ccode = ConnWriteStr(client->conn, MSG5005DBLIBERR); - goto finish; - case 1: - if (!STORE_IS_FOLDER(child.type)) { - FindPathToDocument(client, child.collection, child.guid, path, sizeof(path)); - f = fopen(path, "rb"); - if (!f) { - ccode = ConnWriteStr(client->conn, MSG4224CANTREAD); - goto finish; - } - ccode = SearchDocument(client, &child, query, f); - fclose(f); - } - break; + for (guint index = 0; index < documents->len; index++) { + StoreObject *child = &g_array_index(documents, StoreObject, index); + + if (StoreObjectCheckAuthorization(client, child, STORE_PRIV_READ)) + continue; + FindPathToDocument(client, child->collection_guid, child->guid, path, + sizeof(path)); + f = fopen(path, "rb"); + if (!f) { + g_array_free(documents, TRUE); + return ConnWriteStr(client->conn, MSG4224CANTREAD); + } + ccode = SearchDocument(client, child, query, f); + fclose(f); + if (ccode == -1) { + g_array_free(documents, TRUE); + return -1; } } - */ + g_array_free(documents, TRUE); ccode = ConnWriteStr(client->conn, MSG1000OK); - -//finish: - + return ccode; } diff --git a/src/libs/streamio/CMakeLists.txt b/src/libs/streamio/CMakeLists.txt index a3ac0eb..2529d96 100644 --- a/src/libs/streamio/CMakeLists.txt +++ b/src/libs/streamio/CMakeLists.txt @@ -4,3 +4,20 @@ add_library(bongostreamio SHARED bongostream.c) install(TARGETS bongostreamio DESTINATION ${LIB_INSTALL_DIR}) + +if(BUILD_TESTING) + add_executable(streamio-search-test tests/search-test.c) + if(CMAKE_SYSTEM_NAME STREQUAL "Linux") + target_link_options(streamio-search-test PRIVATE "LINKER:--no-as-needed") + endif() + target_link_libraries(streamio-search-test PRIVATE + bongostreamio + bongoxpl + bongoconnio + bongoutil + bongojson + bongomsgapi + GnuTLS::GnuTLS + Threads::Threads) + add_test(NAME streamio-search COMMAND streamio-search-test) +endif() diff --git a/src/libs/streamio/tests/search-test.c b/src/libs/streamio/tests/search-test.c new file mode 100644 index 0000000..2faa6b3 --- /dev/null +++ b/src/libs/streamio/tests/search-test.c @@ -0,0 +1,73 @@ +/**************************************************************************** + * + * Copyright (c) 2006 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. + * + ****************************************************************************/ + +#include +#include + +#include +#include +#include + +typedef struct { + const char *data; + size_t length; + size_t offset; +} MemorySource; + +static long +ReadMemory(void *opaque, char *buffer, unsigned long maximum) +{ + MemorySource *source = opaque; + size_t remaining = source->length - source->offset; + size_t count = remaining < maximum ? remaining : maximum; + + memcpy(buffer, source->data + source->offset, count); + source->offset += count; + return (long)count; +} + +static int +Search(const char *message, char *header, char *substring) +{ + MemorySource source = { message, strlen(message), 0U }; + + return BongoStreamSearchRfc822Header(ReadMemory, &source, source.length, + header, substring); +} + +int +main(void) +{ + static const char message[] = + "From: Test User \r\n" + "Subject: Local delivery smoke test\r\n" + "X-Folded: first\r\n\tsecond\r\n" + "\r\n" + "The subject word only in this body must not match headers.\r\n"; + + assert(Search(message, "subject", "DELIVERY SMOKE")); + assert(Search(message, "from", "example.test")); + assert(Search(message, "x-folded", "second")); + assert(Search(message, NULL, "local delivery")); + assert(!Search(message, "subject", "only in this body")); + assert(!Search(message, "to", "example.test")); + return 0; +}