1 /* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
2
3 #include "imap-common.h"
4 #include "str.h"
5 #include "ostream.h"
6 #include "imap-base-subject.h"
7 #include "imap-commands.h"
8 #include "imap-search-args.h"
9 #include "mail-thread.h"
10
imap_thread_write(struct mail_thread_iterate_context * iter,string_t * str,bool root)11 static int imap_thread_write(struct mail_thread_iterate_context *iter,
12 string_t *str, bool root)
13 {
14 const struct mail_thread_child_node *node;
15 struct mail_thread_iterate_context *child_iter;
16 unsigned int count;
17 int ret = 0;
18
19 count = mail_thread_iterate_count(iter);
20 if (count == 0)
21 return 0;
22
23 if (count == 1 && !root) {
24 /* only one child - special case to avoid extra parenthesis */
25 node = mail_thread_iterate_next(iter, &child_iter);
26 str_printfa(str, "%u", node->uid);
27 if (child_iter != NULL) {
28 str_append_c(str, ' ');
29 T_BEGIN {
30 ret = imap_thread_write(child_iter, str, FALSE);
31 } T_END;
32 if (mail_thread_iterate_deinit(&child_iter) < 0)
33 return -1;
34 }
35 return ret;
36 }
37
38 while ((node = mail_thread_iterate_next(iter, &child_iter)) != NULL) {
39 if (child_iter == NULL) {
40 /* no children */
41 str_printfa(str, "(%u)", node->uid);
42 } else {
43 /* node with children */
44 str_append_c(str, '(');
45 if (node->uid != 0)
46 str_printfa(str, "%u ", node->uid);
47 T_BEGIN {
48 ret = imap_thread_write(child_iter, str, FALSE);
49 } T_END;
50 if (mail_thread_iterate_deinit(&child_iter) < 0 ||
51 ret < 0)
52 return -1;
53 str_append_c(str, ')');
54 }
55 }
56 return 0;
57 }
58
59 static int
imap_thread_write_reply(struct mail_thread_context * ctx,string_t * str,enum mail_thread_type thread_type,bool write_seqs)60 imap_thread_write_reply(struct mail_thread_context *ctx, string_t *str,
61 enum mail_thread_type thread_type, bool write_seqs)
62 {
63 struct mail_thread_iterate_context *iter;
64 int ret;
65
66 iter = mail_thread_iterate_init(ctx, thread_type, write_seqs);
67 str_append(str, "* THREAD ");
68 T_BEGIN {
69 ret = imap_thread_write(iter, str, TRUE);
70 } T_END;
71 if (mail_thread_iterate_deinit(&iter) < 0)
72 ret = -1;
73
74 str_append(str, "\r\n");
75 return ret;
76 }
77
imap_thread(struct client_command_context * cmd,struct mail_search_args * search_args,enum mail_thread_type thread_type)78 static int imap_thread(struct client_command_context *cmd,
79 struct mail_search_args *search_args,
80 enum mail_thread_type thread_type)
81 {
82 struct mail_thread_context *ctx;
83 string_t *str;
84 int ret;
85
86 i_assert(thread_type == MAIL_THREAD_REFERENCES ||
87 thread_type == MAIL_THREAD_REFS);
88
89 str = str_new(default_pool, 1024);
90 ret = mail_thread_init(cmd->client->mailbox,
91 search_args, &ctx);
92 if (ret == 0) {
93 ret = imap_thread_write_reply(ctx, str, thread_type,
94 !cmd->uid);
95 mail_thread_deinit(&ctx);
96 }
97
98 if (ret == 0)
99 o_stream_nsend(cmd->client->output, str_data(str), str_len(str));
100 str_free(&str);
101 return ret;
102 }
103
104 struct orderedsubject_thread {
105 time_t timestamp;
106 ARRAY_TYPE(uint32_t) msgs;
107 };
108
orderedsubject_thread_cmp(const struct orderedsubject_thread * t1,const struct orderedsubject_thread * t2)109 static int orderedsubject_thread_cmp(const struct orderedsubject_thread *t1,
110 const struct orderedsubject_thread *t2)
111 {
112 const uint32_t *m1, *m2;
113
114 if (t1->timestamp < t2->timestamp)
115 return -1;
116 if (t1->timestamp > t2->timestamp)
117 return 1;
118
119 m1 = array_front(&t1->msgs);
120 m2 = array_front(&t2->msgs);
121 if (*m1 < *m2)
122 return -1;
123 if (*m1 > *m2)
124 return 1;
125 i_unreached();
126 }
127
128 static void
imap_orderedsubject_thread_write(struct ostream * output,string_t * reply,const struct orderedsubject_thread * thread)129 imap_orderedsubject_thread_write(struct ostream *output, string_t *reply,
130 const struct orderedsubject_thread *thread)
131 {
132 const uint32_t *msgs;
133 unsigned int i, count;
134
135 if (str_len(reply) > 128-10) {
136 o_stream_nsend(output, str_data(reply), str_len(reply));
137 str_truncate(reply, 0);
138 }
139
140 msgs = array_get(&thread->msgs, &count);
141 switch (count) {
142 case 1:
143 str_printfa(reply, "(%u)", msgs[0]);
144 break;
145 case 2:
146 str_printfa(reply, "(%u %u)", msgs[0], msgs[1]);
147 break;
148 default:
149 /* (1 (2)(3)) */
150 str_printfa(reply, "(%u ", msgs[0]);
151 for (i = 1; i < count; i++) {
152 if (str_len(reply) > 128-10) {
153 o_stream_nsend(output, str_data(reply),
154 str_len(reply));
155 str_truncate(reply, 0);
156 }
157 str_printfa(reply, "(%u)", msgs[i]);
158 }
159 str_append_c(reply, ')');
160 }
161 }
162
imap_thread_orderedsubject(struct client_command_context * cmd,struct mail_search_args * search_args)163 static int imap_thread_orderedsubject(struct client_command_context *cmd,
164 struct mail_search_args *search_args)
165 {
166 static const enum mail_sort_type sort_program[] = {
167 MAIL_SORT_SUBJECT,
168 MAIL_SORT_DATE,
169 0
170 };
171 struct mailbox_transaction_context *trans;
172 struct mail_search_context *search_ctx;
173 struct mail *mail;
174 string_t *prev_subject, *reply;
175 const char *subject, *base_subject;
176 pool_t pool;
177 ARRAY(struct orderedsubject_thread) threads;
178 const struct orderedsubject_thread *thread;
179 struct orderedsubject_thread *cur_thread = NULL;
180 uint32_t num;
181 bool reply_or_fw;
182 int ret, tz;
183
184 prev_subject = str_new(default_pool, 128);
185
186 /* first read all of the threads into memory */
187 pool = pool_alloconly_create("orderedsubject thread", 1024);
188 i_array_init(&threads, 128);
189 trans = mailbox_transaction_begin(cmd->client->mailbox, 0,
190 imap_client_command_get_reason(cmd));
191 search_ctx = mailbox_search_init(trans, search_args, sort_program,
192 0, NULL);
193 while (mailbox_search_next(search_ctx, &mail)) {
194 if (mail_get_first_header(mail, "Subject", &subject) <= 0)
195 subject = "";
196 T_BEGIN {
197 base_subject = imap_get_base_subject_cased(
198 pool_datastack_create(), subject,
199 &reply_or_fw);
200 if (strcmp(str_c(prev_subject), base_subject) != 0) {
201 /* thread changed */
202 cur_thread = NULL;
203 }
204 str_truncate(prev_subject, 0);
205 str_append(prev_subject, base_subject);
206 } T_END;
207
208 if (cur_thread == NULL) {
209 /* starting a new thread. get the first message's
210 date */
211 cur_thread = array_append_space(&threads);
212 if (mail_get_date(mail, &cur_thread->timestamp,
213 &tz) == 0 &&
214 cur_thread->timestamp == 0) {
215 (void)mail_get_received_date(mail,
216 &cur_thread->timestamp);
217 }
218 p_array_init(&cur_thread->msgs, pool, 4);
219 }
220 num = cmd->uid ? mail->uid : mail->seq;
221 array_push_back(&cur_thread->msgs, &num);
222 }
223 str_free(&prev_subject);
224 ret = mailbox_search_deinit(&search_ctx);
225 (void)mailbox_transaction_commit(&trans);
226 if (ret < 0) {
227 array_free(&threads);
228 pool_unref(&pool);
229 return -1;
230 }
231
232 /* sort the threads by their first message's timestamp */
233 array_sort(&threads, orderedsubject_thread_cmp);
234
235 /* write the threads to client */
236 reply = t_str_new(128);
237 str_append(reply, "* THREAD ");
238 array_foreach(&threads, thread) {
239 imap_orderedsubject_thread_write(cmd->client->output,
240 reply, thread);
241 }
242 str_append(reply, "\r\n");
243 o_stream_nsend(cmd->client->output, str_data(reply), str_len(reply));
244
245 array_free(&threads);
246 pool_unref(&pool);
247 return 0;
248 }
249
cmd_thread(struct client_command_context * cmd)250 bool cmd_thread(struct client_command_context *cmd)
251 {
252 struct client *client = cmd->client;
253 enum mail_thread_type thread_type;
254 struct mail_search_args *sargs;
255 const struct imap_arg *args;
256 const char *charset, *str;
257 int ret;
258
259 if (!client_read_args(cmd, 0, 0, &args))
260 return FALSE;
261
262 if (!client_verify_open_mailbox(cmd))
263 return TRUE;
264
265 if (!imap_arg_get_astring(&args[0], &str) ||
266 !imap_arg_get_astring(&args[1], &charset)) {
267 client_send_command_error(cmd, "Invalid arguments.");
268 return TRUE;
269 }
270 args += 2;
271
272 if (!mail_thread_type_parse(str, &thread_type)) {
273 client_send_command_error(cmd, "Unknown thread algorithm.");
274 return TRUE;
275 }
276
277 ret = imap_search_args_build(cmd, args, charset, &sargs);
278 if (ret <= 0)
279 return ret < 0;
280
281 if (thread_type != MAIL_THREAD_ORDEREDSUBJECT)
282 ret = imap_thread(cmd, sargs, thread_type);
283 else
284 ret = imap_thread_orderedsubject(cmd, sargs);
285 mail_search_args_unref(&sargs);
286 if (ret < 0) {
287 client_send_box_error(cmd, client->mailbox);
288 return TRUE;
289 }
290
291 return cmd_sync(cmd, MAILBOX_SYNC_FLAG_FAST |
292 (cmd->uid ? 0 : MAILBOX_SYNC_FLAG_NO_EXPUNGES),
293 0, "OK Thread completed.");
294 }
295