summaryrefslogtreecommitdiffstats
path: root/src/lib-storage/test-mail.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib-storage/test-mail.c')
-rw-r--r--src/lib-storage/test-mail.c506
1 files changed, 506 insertions, 0 deletions
diff --git a/src/lib-storage/test-mail.c b/src/lib-storage/test-mail.c
new file mode 100644
index 0000000..d68fda7
--- /dev/null
+++ b/src/lib-storage/test-mail.c
@@ -0,0 +1,506 @@
+/* Copyright (c) 2020 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "test-common.h"
+#include "istream.h"
+#include "master-service.h"
+#include "message-size.h"
+#include "test-mail-storage-common.h"
+
+static struct event *test_event;
+
+static int
+test_mail_save_trans(struct mailbox_transaction_context *trans,
+ struct istream *input)
+{
+ struct mail_save_context *save_ctx;
+ int ret;
+
+ save_ctx = mailbox_save_alloc(trans);
+ if (mailbox_save_begin(&save_ctx, input) < 0)
+ return -1;
+ do {
+ if (mailbox_save_continue(save_ctx) < 0) {
+ mailbox_save_cancel(&save_ctx);
+ return -1;
+ }
+ } while ((ret = i_stream_read(input)) > 0);
+ i_assert(ret == -1);
+ i_assert(input->stream_errno == 0);
+
+ return mailbox_save_finish(&save_ctx);
+}
+
+static void test_mail_save(struct mailbox *box, const char *mail_input)
+{
+ struct mailbox_transaction_context *trans;
+ struct istream *input;
+ int ret;
+
+ input = i_stream_create_from_data(mail_input, strlen(mail_input));
+ trans = mailbox_transaction_begin(box,
+ MAILBOX_TRANSACTION_FLAG_EXTERNAL, __func__);
+ ret = test_mail_save_trans(trans, input);
+ i_stream_unref(&input);
+ if (ret < 0)
+ mailbox_transaction_rollback(&trans);
+ else
+ ret = mailbox_transaction_commit(&trans);
+ if (ret < 0) {
+ i_fatal("Failed to save mail: %s",
+ mailbox_get_last_internal_error(box, NULL));
+ }
+ if (mailbox_sync(box, 0) < 0)
+ i_fatal("Failed to sync mailbox: %s",
+ mailbox_get_last_internal_error(box, NULL));
+}
+
+static void test_mail_remove_keywords(struct mailbox *box)
+{
+ struct mailbox_transaction_context *trans;
+ const char *keywords[] = { NULL };
+ struct mail_keywords *kw;
+ struct mail *mail;
+
+ trans = mailbox_transaction_begin(box, 0, __func__);
+ mail = mail_alloc(trans, 0, NULL);
+ mail_set_seq(mail, 1);
+ if (mailbox_keywords_create(box, keywords, &kw) < 0)
+ i_fatal("mailbox_keywords_create() failed: %s",
+ mailbox_get_last_internal_error(box, NULL));
+ mail_update_keywords(mail, MODIFY_REPLACE, kw);
+ mailbox_keywords_unref(&kw);
+ mail_free(&mail);
+ if (mailbox_transaction_commit(&trans) < 0) {
+ i_fatal("Failed to update flags: %s",
+ mailbox_get_last_internal_error(box, NULL));
+ }
+ if (mailbox_sync(box, 0) < 0)
+ i_fatal("Failed to sync mailbox: %s",
+ mailbox_get_last_internal_error(box, NULL));
+}
+
+static struct mailbox_header_lookup_ctx *
+test_mail_fetch_get_random_headers(struct mailbox *box)
+{
+ ARRAY_TYPE(const_string) headers_arr;
+ const char *potential_headers[] = {
+ "From", "To", "Subject", "Nonexistent",
+ };
+
+ t_array_init(&headers_arr, N_ELEMENTS(potential_headers)+1);
+ do {
+ for (unsigned int i = 0; i < N_ELEMENTS(potential_headers); i++) {
+ if (i_rand_limit(2) == 0)
+ array_push_back(&headers_arr, &potential_headers[i]);
+ }
+ } while (array_count(&headers_arr) == 0);
+
+ array_append_zero(&headers_arr);
+ return mailbox_header_lookup_init(box, array_idx(&headers_arr, 0));
+}
+
+static void
+test_mail_fetch_field(struct mail *mail, enum mail_fetch_field field)
+{
+ struct message_part *parts;
+ struct message_size hdr_size, body_size;
+ struct istream *input;
+ const char *str;
+ time_t t;
+ uoff_t size;
+ unsigned int lines;
+ bool binary;
+ int tz, ret = 0;
+
+ e_debug(test_event, "field=0x%x", field);
+ switch (field) {
+ case MAIL_FETCH_FLAGS:
+ (void)mail_get_flags(mail);
+ (void)mail_get_keywords(mail);
+ break;
+ case MAIL_FETCH_MESSAGE_PARTS:
+ ret = mail_get_parts(mail, &parts);
+ break;
+ case MAIL_FETCH_STREAM_HEADER:
+ ret = mail_get_hdr_stream(mail,
+ i_rand_limit(2) == 0 ? &hdr_size : NULL, &input);
+ break;
+ case MAIL_FETCH_STREAM_BODY:
+ ret = mail_get_stream(mail,
+ i_rand_limit(2) == 0 ? &hdr_size : NULL,
+ i_rand_limit(2) == 0 ? &body_size : NULL, &input);
+ break;
+ case MAIL_FETCH_DATE:
+ ret = mail_get_date(mail, &t, &tz);
+ break;
+ case MAIL_FETCH_RECEIVED_DATE:
+ ret = mail_get_received_date(mail, &t);
+ break;
+ case MAIL_FETCH_SAVE_DATE:
+ ret = mail_get_save_date(mail, &t);
+ break;
+ case MAIL_FETCH_PHYSICAL_SIZE:
+ ret = mail_get_physical_size(mail, &size);
+ break;
+ case MAIL_FETCH_VIRTUAL_SIZE:
+ ret = mail_get_virtual_size(mail, &size);
+ break;
+ case MAIL_FETCH_NUL_STATE:
+ /* nothing to do */
+ break;
+ case MAIL_FETCH_STREAM_BINARY:
+ if ((ret = mail_get_parts(mail, &parts)) < 0)
+ break;
+
+ if (i_rand_limit(2) == 0) {
+ ret = mail_get_binary_stream(mail, parts,
+ i_rand_limit(2) == 0,
+ &size, &binary, &input);
+ if (ret == 0)
+ i_stream_unref(&input);
+ } else {
+ ret = mail_get_binary_size(mail, parts,
+ i_rand_limit(2) == 0,
+ &size, &lines);
+ }
+ break;
+ case MAIL_FETCH_IMAP_BODY:
+ case MAIL_FETCH_IMAP_BODYSTRUCTURE:
+ case MAIL_FETCH_IMAP_ENVELOPE:
+ case MAIL_FETCH_FROM_ENVELOPE:
+ case MAIL_FETCH_HEADER_MD5:
+ case MAIL_FETCH_STORAGE_ID:
+ case MAIL_FETCH_UIDL_BACKEND:
+ case MAIL_FETCH_MAILBOX_NAME:
+ case MAIL_FETCH_SEARCH_RELEVANCY:
+ case MAIL_FETCH_GUID:
+ case MAIL_FETCH_POP3_ORDER:
+ case MAIL_FETCH_REFCOUNT:
+ case MAIL_FETCH_BODY_SNIPPET:
+ case MAIL_FETCH_REFCOUNT_ID:
+ ret = mail_get_special(mail, field, &str);
+ break;
+ }
+ if (ret < 0) {
+ const char *errstr;
+ enum mail_error error;
+
+ errstr = mailbox_get_last_internal_error(mail->box, &error);
+ if (error != MAIL_ERROR_LOOKUP_ABORTED)
+ i_error("Failed to fetch field 0x%x: %s", field, errstr);
+ }
+}
+
+static void
+test_mail_fetch_headers(struct mail *mail,
+ struct mailbox_header_lookup_ctx *headers)
+{
+ struct istream *input;
+
+ e_debug(test_event, "header fields");
+ if (mail_get_header_stream(mail, headers, &input) < 0) {
+ const char *errstr;
+ enum mail_error error;
+
+ errstr = mailbox_get_last_internal_error(mail->box, &error);
+ if (error != MAIL_ERROR_LOOKUP_ABORTED)
+ i_error("Failed to fetch headers: %s", errstr);
+ return;
+ }
+ while (i_stream_read(input) > 0)
+ i_stream_skip(input, i_stream_get_data_size(input));
+}
+
+static void test_mail_random_fetch(struct mailbox *box, uint32_t seq)
+{
+ const enum mail_fetch_field potential_fields[] = {
+ MAIL_FETCH_FLAGS,
+ MAIL_FETCH_MESSAGE_PARTS,
+ MAIL_FETCH_STREAM_HEADER,
+ MAIL_FETCH_STREAM_BODY,
+ MAIL_FETCH_DATE,
+ MAIL_FETCH_RECEIVED_DATE,
+ MAIL_FETCH_SAVE_DATE,
+ MAIL_FETCH_PHYSICAL_SIZE,
+ MAIL_FETCH_VIRTUAL_SIZE,
+ MAIL_FETCH_NUL_STATE,
+ MAIL_FETCH_STREAM_BINARY,
+ MAIL_FETCH_IMAP_BODY,
+ MAIL_FETCH_IMAP_BODYSTRUCTURE,
+ MAIL_FETCH_IMAP_ENVELOPE,
+ MAIL_FETCH_FROM_ENVELOPE,
+ MAIL_FETCH_HEADER_MD5,
+ MAIL_FETCH_STORAGE_ID,
+ MAIL_FETCH_UIDL_BACKEND,
+ MAIL_FETCH_MAILBOX_NAME,
+ MAIL_FETCH_SEARCH_RELEVANCY,
+ MAIL_FETCH_GUID,
+ MAIL_FETCH_POP3_ORDER,
+ MAIL_FETCH_REFCOUNT,
+ MAIL_FETCH_BODY_SNIPPET,
+ MAIL_FETCH_REFCOUNT_ID,
+ };
+ struct mailbox_transaction_context *trans;
+ struct mail *mail;
+ enum mail_fetch_field wanted_fields = 0;
+ struct mailbox_header_lookup_ctx *headers = NULL;
+
+ if (i_rand_limit(2) == 0)
+ wanted_fields = i_rand();
+ if (i_rand_limit(2) == 0)
+ headers = test_mail_fetch_get_random_headers(box);
+ trans = mailbox_transaction_begin(box, 0, __func__);
+ e_debug(test_event, "wanted_fields=%u wanted_headers=%p", wanted_fields, headers);
+ mail = mail_alloc(trans, wanted_fields, headers);
+ mailbox_header_lookup_unref(&headers);
+ mail_set_seq(mail, seq);
+
+ for (unsigned int i = 0; i < 5; i++) {
+ unsigned int fetch_field_idx =
+ i_rand_limit(N_ELEMENTS(potential_fields) + 1);
+
+ if (i_rand_limit(3) == 0)
+ mail->lookup_abort = 1+i_rand_limit(MAIL_LOOKUP_ABORT_NOT_IN_CACHE_START_CACHING);
+ if (fetch_field_idx < N_ELEMENTS(potential_fields)) {
+ test_mail_fetch_field(mail,
+ potential_fields[fetch_field_idx]);
+ } else {
+ headers = test_mail_fetch_get_random_headers(box);
+ test_mail_fetch_headers(mail, headers);
+ mailbox_header_lookup_unref(&headers);
+ }
+ mail->lookup_abort = MAIL_LOOKUP_ABORT_NEVER;
+ }
+ mail_free(&mail);
+ if (mailbox_transaction_commit(&trans) < 0) {
+ i_fatal("Failed to commit transaction: %s",
+ mailbox_get_last_internal_error(box, NULL));
+ }
+}
+
+static void test_mail_random_access(void)
+{
+ struct test_mail_storage_ctx *ctx;
+ const char *const potential_never_cache_fields[] = {
+ "",
+ "flags",
+ "mime.parts",
+ "imap.body",
+ "imap.bodystructure",
+ };
+ unsigned int never_cache_field_idx =
+ i_rand_limit(N_ELEMENTS(potential_never_cache_fields));
+ const char *never_cache_fields =
+ potential_never_cache_fields[never_cache_field_idx];
+ struct test_mail_storage_settings set = {
+ .driver = "sdbox",
+ .extra_input = (const char *const[]) {
+ "mail_attachment_detection_options=add-flags",
+ t_strconcat("mail_never_cache_fields=",
+ never_cache_fields, NULL),
+ NULL
+ },
+ };
+ struct mailbox *box;
+
+ test_begin("mail");
+ ctx = test_mail_storage_init();
+ test_mail_storage_init_user(ctx, &set);
+ e_debug(test_event, "mail_never_cache_fields=%s", never_cache_fields);
+ for (unsigned int i = 0; i < 20; i++) {
+ box = mailbox_alloc(ctx->user->namespaces->list, "INBOX", 0);
+ if (mailbox_open(box) < 0)
+ i_fatal("Failed to open mailbox: %s",
+ mailbox_get_last_internal_error(box, NULL));
+ test_mail_save(box,
+ "From: <test1@example.com>\n"
+ "To: <test1-dest@example.com>\n"
+ "Subject: test subject\n"
+ "\n"
+ "test body\n");
+ test_mail_remove_keywords(box);
+ e_debug(test_event, "--------------");
+ for (unsigned int j = 0; j < 3; j++)
+ test_mail_random_fetch(box, 1);
+ if (mailbox_delete(box) < 0)
+ i_fatal("Failed to delete mailbox: %s",
+ mailbox_get_last_internal_error(box, NULL));
+ mailbox_free(&box);
+ }
+ test_mail_storage_deinit_user(ctx);
+ test_mail_storage_deinit(&ctx);
+ test_end();
+}
+
+static void test_attachment_flags_during_header_fetch(void)
+{
+ struct test_mail_storage_ctx *ctx;
+ struct test_mail_storage_settings set = {
+ .driver = "sdbox",
+ .extra_input = (const char *const[]) {
+ "mail_attachment_detection_options=add-flags",
+ "mail_never_cache_fields=mime.parts",
+ NULL
+ },
+ };
+
+ test_begin("mail attachment flags during header fetch");
+ ctx = test_mail_storage_init();
+ test_mail_storage_init_user(ctx, &set);
+
+ struct mailbox *box =
+ mailbox_alloc(ctx->user->namespaces->list, "INBOX", 0);
+ test_assert(mailbox_open(box) == 0);
+
+#define TEST_HDR_FROM "From: <test1@example.com>\r\n"
+ test_mail_save(box,
+ TEST_HDR_FROM
+ "\r\n"
+ "test body\n");
+ /* Remove the $HasNoAttachment keyword */
+ test_mail_remove_keywords(box);
+
+ struct mailbox_transaction_context *trans =
+ mailbox_transaction_begin(box, 0, __func__);
+ struct mail *mail = mail_alloc(trans, 0, NULL);
+ mail_set_seq(mail, 1);
+
+ const char *from_headers[] = { "From", NULL };
+ struct mailbox_header_lookup_ctx *headers =
+ mailbox_header_lookup_init(box, from_headers);
+
+ struct istream *input;
+ const unsigned char *data;
+ size_t size;
+ test_assert(mail_get_header_stream(mail, headers, &input) == 0);
+ test_assert(i_stream_read_more(input, &data, &size) == 1);
+ /* TEST_HDR_FROM */
+ test_assert(size == strlen(TEST_HDR_FROM) &&
+ memcmp(data, TEST_HDR_FROM, strlen(TEST_HDR_FROM)) == 0);
+ i_stream_skip(input, size);
+ test_assert(i_stream_read_more(input, &data, &size) == 1);
+ test_assert(size == 2 && memcmp(data, "\r\n", 2) == 0);
+ i_stream_skip(input, size);
+ test_assert(i_stream_read_more(input, &data, &size) == -1);
+
+ mailbox_header_lookup_unref(&headers);
+ mail_free(&mail);
+ test_assert(mailbox_transaction_commit(&trans) == 0);
+ mailbox_free(&box);
+ test_mail_storage_deinit_user(ctx);
+ test_mail_storage_deinit(&ctx);
+ test_end();
+}
+
+static void test_bodystructure_reparsing(void)
+{
+ struct test_mail_storage_ctx *ctx;
+ struct test_mail_storage_settings set = {
+ .driver = "sdbox",
+ .extra_input = (const char *const[]) {
+ "mail_attachment_detection_options=add-flags",
+ "mail_never_cache_fields=flags",
+ NULL
+ },
+ };
+ const char *value;
+
+ test_begin("mail bodystructure reparsing");
+ ctx = test_mail_storage_init();
+ test_mail_storage_init_user(ctx, &set);
+
+ struct mailbox *box =
+ mailbox_alloc(ctx->user->namespaces->list, "INBOX", 0);
+ test_assert(mailbox_open(box) == 0);
+
+ test_mail_save(box,
+ "From: <test1@example.com>\r\n"
+ "\r\n"
+ "test body\n");
+
+ struct mailbox_transaction_context *trans =
+ mailbox_transaction_begin(box, 0, __func__);
+ struct mail *mail = mail_alloc(trans, MAIL_FETCH_IMAP_BODYSTRUCTURE, NULL);
+ mail_set_seq(mail, 1);
+
+ /* start parsing header */
+ test_assert(mail_get_first_header(mail, "From", &value) == 1);
+ /* fetching snippet triggers re-parsing the header */
+ test_assert(mail_get_special(mail, MAIL_FETCH_BODY_SNIPPET, &value) == 0);
+
+ mail_free(&mail);
+ test_assert(mailbox_transaction_commit(&trans) == 0);
+ mailbox_free(&box);
+ test_mail_storage_deinit_user(ctx);
+ test_mail_storage_deinit(&ctx);
+ test_end();
+}
+
+static void test_bodystructure_corruption_reparsing(void)
+{
+ struct test_mail_storage_ctx *ctx;
+ struct test_mail_storage_settings set = {
+ .driver = "sdbox",
+ };
+ const char *value;
+
+ test_begin("bodystructure corruption reparsing");
+ ctx = test_mail_storage_init();
+ test_mail_storage_init_user(ctx, &set);
+
+ struct mailbox *box =
+ mailbox_alloc(ctx->user->namespaces->list, "INBOX", 0);
+ test_assert(mailbox_open(box) == 0);
+
+ test_mail_save(box,
+ "From: <test1@example.com>\r\n"
+ "\r\n"
+ "test body\n");
+
+ struct mailbox_transaction_context *trans =
+ mailbox_transaction_begin(box, 0, __func__);
+ struct mail *mail = mail_alloc(trans, 0, NULL);
+ mail_set_seq(mail, 1);
+
+ test_assert(mail_get_special(mail, MAIL_FETCH_IMAP_BODY, &value) == 0);
+ test_expect_error_string("Mailbox INBOX: Deleting corrupted cache record uid=1: UID 1: Broken MIME parts in mailbox INBOX: test");
+ mail_set_cache_corrupted(mail, MAIL_FETCH_MESSAGE_PARTS, "test");
+ test_expect_no_more_errors();
+ test_assert(mail_get_special(mail, MAIL_FETCH_IMAP_BODYSTRUCTURE, &value) == 0);
+
+ mail_free(&mail);
+ test_assert(mailbox_transaction_commit(&trans) == 0);
+ mailbox_free(&box);
+ test_mail_storage_deinit_user(ctx);
+ test_mail_storage_deinit(&ctx);
+ test_end();
+}
+
+int main(int argc, char **argv)
+{
+ void (*const tests[])(void) = {
+ test_mail_random_access,
+ test_attachment_flags_during_header_fetch,
+ test_bodystructure_reparsing,
+ test_bodystructure_corruption_reparsing,
+ NULL
+ };
+ int ret;
+
+ master_service = master_service_init("test-mail",
+ MASTER_SERVICE_FLAG_STANDALONE |
+ MASTER_SERVICE_FLAG_DONT_SEND_STATS |
+ MASTER_SERVICE_FLAG_NO_CONFIG_SETTINGS |
+ MASTER_SERVICE_FLAG_NO_SSL_INIT |
+ MASTER_SERVICE_FLAG_NO_INIT_DATASTACK_FRAME,
+ &argc, &argv, "");
+
+ test_event = event_create(NULL);
+ if (null_strcmp(argv[1], "-D") == 0)
+ event_set_forced_debug(test_event, TRUE);
+ ret = test_run(tests);
+ event_unref(&test_event);
+ master_service_deinit(&master_service);
+ return ret;
+}