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