diff options
Diffstat (limited to 'src/lib-storage/test-mail.c')
-rw-r--r-- | src/lib-storage/test-mail.c | 506 |
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; +} |