diff options
Diffstat (limited to 'src/imap/cmd-thread.c')
-rw-r--r-- | src/imap/cmd-thread.c | 294 |
1 files changed, 294 insertions, 0 deletions
diff --git a/src/imap/cmd-thread.c b/src/imap/cmd-thread.c new file mode 100644 index 0000000..64388d5 --- /dev/null +++ b/src/imap/cmd-thread.c @@ -0,0 +1,294 @@ +/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */ + +#include "imap-common.h" +#include "str.h" +#include "ostream.h" +#include "imap-base-subject.h" +#include "imap-commands.h" +#include "imap-search-args.h" +#include "mail-thread.h" + +static int imap_thread_write(struct mail_thread_iterate_context *iter, + string_t *str, bool root) +{ + const struct mail_thread_child_node *node; + struct mail_thread_iterate_context *child_iter; + unsigned int count; + int ret = 0; + + count = mail_thread_iterate_count(iter); + if (count == 0) + return 0; + + if (count == 1 && !root) { + /* only one child - special case to avoid extra parenthesis */ + node = mail_thread_iterate_next(iter, &child_iter); + str_printfa(str, "%u", node->uid); + if (child_iter != NULL) { + str_append_c(str, ' '); + T_BEGIN { + ret = imap_thread_write(child_iter, str, FALSE); + } T_END; + if (mail_thread_iterate_deinit(&child_iter) < 0) + return -1; + } + return ret; + } + + while ((node = mail_thread_iterate_next(iter, &child_iter)) != NULL) { + if (child_iter == NULL) { + /* no children */ + str_printfa(str, "(%u)", node->uid); + } else { + /* node with children */ + str_append_c(str, '('); + if (node->uid != 0) + str_printfa(str, "%u ", node->uid); + T_BEGIN { + ret = imap_thread_write(child_iter, str, FALSE); + } T_END; + if (mail_thread_iterate_deinit(&child_iter) < 0 || + ret < 0) + return -1; + str_append_c(str, ')'); + } + } + return 0; +} + +static int +imap_thread_write_reply(struct mail_thread_context *ctx, string_t *str, + enum mail_thread_type thread_type, bool write_seqs) +{ + struct mail_thread_iterate_context *iter; + int ret; + + iter = mail_thread_iterate_init(ctx, thread_type, write_seqs); + str_append(str, "* THREAD "); + T_BEGIN { + ret = imap_thread_write(iter, str, TRUE); + } T_END; + if (mail_thread_iterate_deinit(&iter) < 0) + ret = -1; + + str_append(str, "\r\n"); + return ret; +} + +static int imap_thread(struct client_command_context *cmd, + struct mail_search_args *search_args, + enum mail_thread_type thread_type) +{ + struct mail_thread_context *ctx; + string_t *str; + int ret; + + i_assert(thread_type == MAIL_THREAD_REFERENCES || + thread_type == MAIL_THREAD_REFS); + + str = str_new(default_pool, 1024); + ret = mail_thread_init(cmd->client->mailbox, + search_args, &ctx); + if (ret == 0) { + ret = imap_thread_write_reply(ctx, str, thread_type, + !cmd->uid); + mail_thread_deinit(&ctx); + } + + if (ret == 0) + o_stream_nsend(cmd->client->output, str_data(str), str_len(str)); + str_free(&str); + return ret; +} + +struct orderedsubject_thread { + time_t timestamp; + ARRAY_TYPE(uint32_t) msgs; +}; + +static int orderedsubject_thread_cmp(const struct orderedsubject_thread *t1, + const struct orderedsubject_thread *t2) +{ + const uint32_t *m1, *m2; + + if (t1->timestamp < t2->timestamp) + return -1; + if (t1->timestamp > t2->timestamp) + return 1; + + m1 = array_front(&t1->msgs); + m2 = array_front(&t2->msgs); + if (*m1 < *m2) + return -1; + if (*m1 > *m2) + return 1; + i_unreached(); +} + +static void +imap_orderedsubject_thread_write(struct ostream *output, string_t *reply, + const struct orderedsubject_thread *thread) +{ + const uint32_t *msgs; + unsigned int i, count; + + if (str_len(reply) > 128-10) { + o_stream_nsend(output, str_data(reply), str_len(reply)); + str_truncate(reply, 0); + } + + msgs = array_get(&thread->msgs, &count); + switch (count) { + case 1: + str_printfa(reply, "(%u)", msgs[0]); + break; + case 2: + str_printfa(reply, "(%u %u)", msgs[0], msgs[1]); + break; + default: + /* (1 (2)(3)) */ + str_printfa(reply, "(%u ", msgs[0]); + for (i = 1; i < count; i++) { + if (str_len(reply) > 128-10) { + o_stream_nsend(output, str_data(reply), + str_len(reply)); + str_truncate(reply, 0); + } + str_printfa(reply, "(%u)", msgs[i]); + } + str_append_c(reply, ')'); + } +} + +static int imap_thread_orderedsubject(struct client_command_context *cmd, + struct mail_search_args *search_args) +{ + static const enum mail_sort_type sort_program[] = { + MAIL_SORT_SUBJECT, + MAIL_SORT_DATE, + 0 + }; + struct mailbox_transaction_context *trans; + struct mail_search_context *search_ctx; + struct mail *mail; + string_t *prev_subject, *reply; + const char *subject, *base_subject; + pool_t pool; + ARRAY(struct orderedsubject_thread) threads; + const struct orderedsubject_thread *thread; + struct orderedsubject_thread *cur_thread = NULL; + uint32_t num; + bool reply_or_fw; + int ret, tz; + + prev_subject = str_new(default_pool, 128); + + /* first read all of the threads into memory */ + pool = pool_alloconly_create("orderedsubject thread", 1024); + i_array_init(&threads, 128); + trans = mailbox_transaction_begin(cmd->client->mailbox, 0, + imap_client_command_get_reason(cmd)); + search_ctx = mailbox_search_init(trans, search_args, sort_program, + 0, NULL); + while (mailbox_search_next(search_ctx, &mail)) { + if (mail_get_first_header(mail, "Subject", &subject) <= 0) + subject = ""; + T_BEGIN { + base_subject = imap_get_base_subject_cased( + pool_datastack_create(), subject, + &reply_or_fw); + if (strcmp(str_c(prev_subject), base_subject) != 0) { + /* thread changed */ + cur_thread = NULL; + } + str_truncate(prev_subject, 0); + str_append(prev_subject, base_subject); + } T_END; + + if (cur_thread == NULL) { + /* starting a new thread. get the first message's + date */ + cur_thread = array_append_space(&threads); + if (mail_get_date(mail, &cur_thread->timestamp, + &tz) == 0 && + cur_thread->timestamp == 0) { + (void)mail_get_received_date(mail, + &cur_thread->timestamp); + } + p_array_init(&cur_thread->msgs, pool, 4); + } + num = cmd->uid ? mail->uid : mail->seq; + array_push_back(&cur_thread->msgs, &num); + } + str_free(&prev_subject); + ret = mailbox_search_deinit(&search_ctx); + (void)mailbox_transaction_commit(&trans); + if (ret < 0) { + array_free(&threads); + pool_unref(&pool); + return -1; + } + + /* sort the threads by their first message's timestamp */ + array_sort(&threads, orderedsubject_thread_cmp); + + /* write the threads to client */ + reply = t_str_new(128); + str_append(reply, "* THREAD "); + array_foreach(&threads, thread) { + imap_orderedsubject_thread_write(cmd->client->output, + reply, thread); + } + str_append(reply, "\r\n"); + o_stream_nsend(cmd->client->output, str_data(reply), str_len(reply)); + + array_free(&threads); + pool_unref(&pool); + return 0; +} + +bool cmd_thread(struct client_command_context *cmd) +{ + struct client *client = cmd->client; + enum mail_thread_type thread_type; + struct mail_search_args *sargs; + const struct imap_arg *args; + const char *charset, *str; + int ret; + + if (!client_read_args(cmd, 0, 0, &args)) + return FALSE; + + if (!client_verify_open_mailbox(cmd)) + return TRUE; + + if (!imap_arg_get_astring(&args[0], &str) || + !imap_arg_get_astring(&args[1], &charset)) { + client_send_command_error(cmd, "Invalid arguments."); + return TRUE; + } + args += 2; + + if (!mail_thread_type_parse(str, &thread_type)) { + client_send_command_error(cmd, "Unknown thread algorithm."); + return TRUE; + } + + ret = imap_search_args_build(cmd, args, charset, &sargs); + if (ret <= 0) + return ret < 0; + + if (thread_type != MAIL_THREAD_ORDEREDSUBJECT) + ret = imap_thread(cmd, sargs, thread_type); + else + ret = imap_thread_orderedsubject(cmd, sargs); + mail_search_args_unref(&sargs); + if (ret < 0) { + client_send_box_error(cmd, client->mailbox); + return TRUE; + } + + return cmd_sync(cmd, MAILBOX_SYNC_FLAG_FAST | + (cmd->uid ? 0 : MAILBOX_SYNC_FLAG_NO_EXPUNGES), + 0, "OK Thread completed."); +} |