summaryrefslogtreecommitdiffstats
path: root/src/imap/cmd-thread.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/imap/cmd-thread.c')
-rw-r--r--src/imap/cmd-thread.c294
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.");
+}