/* 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."); }