1 /* Copyright (c) 2002-2018 Pigeonhole authors, see the included COPYING file
2 */
3
4 #include "lib.h"
5 #include "str-sanitize.h"
6 #include "array.h"
7 #include "mail-storage.h"
8
9 #include "sieve-code.h"
10 #include "sieve-stringlist.h"
11 #include "sieve-extensions.h"
12 #include "sieve-commands.h"
13 #include "sieve-result.h"
14 #include "sieve-validator.h"
15 #include "sieve-generator.h"
16 #include "sieve-interpreter.h"
17 #include "sieve-actions.h"
18 #include "sieve-dump.h"
19
20 #include "ext-imap4flags-common.h"
21
22 #include <ctype.h>
23
24 /*
25 * Flags tagged argument
26 */
27
28 static bool
29 tag_flags_validate(struct sieve_validator *valdtr,
30 struct sieve_ast_argument **arg, struct sieve_command *cmd);
31 static bool
32 tag_flags_validate_persistent(struct sieve_validator *valdtr,
33 struct sieve_command *cmd,
34 const struct sieve_extension *ext);
35 static bool
36 tag_flags_generate(const struct sieve_codegen_env *cgenv,
37 struct sieve_ast_argument *arg, struct sieve_command *cmd);
38
39 const struct sieve_argument_def tag_flags = {
40 .identifier = "flags",
41 .validate = tag_flags_validate,
42 .generate = tag_flags_generate,
43 };
44
45 const struct sieve_argument_def tag_flags_implicit = {
46 .identifier = "flags-implicit",
47 .validate_persistent = tag_flags_validate_persistent,
48 .generate = tag_flags_generate,
49 };
50
51 /*
52 * Side effect
53 */
54
55 static bool
56 seff_flags_dump_context(const struct sieve_side_effect *seffect,
57 const struct sieve_dumptime_env *denv,
58 sieve_size_t *address);
59 static int
60 seff_flags_read_context(const struct sieve_side_effect *seffect,
61 const struct sieve_runtime_env *renv,
62 sieve_size_t *address, void **context);
63
64 static int
65 seff_flags_merge(const struct sieve_runtime_env *renv,
66 const struct sieve_action *action,
67 const struct sieve_side_effect *old_seffect,
68 const struct sieve_side_effect *new_seffect,
69 void **old_context);
70
71 static void
72 seff_flags_print(const struct sieve_side_effect *seffect,
73 const struct sieve_action *action,
74 const struct sieve_result_print_env *rpenv, bool *keep);
75
76 static int
77 seff_flags_pre_execute(const struct sieve_side_effect *seffect,
78 const struct sieve_action_exec_env *aenv,
79 void *tr_context, void **se_tr_context);
80
81 const struct sieve_side_effect_def flags_side_effect = {
82 SIEVE_OBJECT("flags", &flags_side_effect_operand, 0),
83 .to_action = &act_store,
84 .dump_context = seff_flags_dump_context,
85 .read_context = seff_flags_read_context,
86 .merge = seff_flags_merge,
87 .print = seff_flags_print,
88 .pre_execute = seff_flags_pre_execute,
89 };
90
91 /*
92 * Operand
93 */
94
95 static const struct sieve_extension_objects ext_side_effects =
96 SIEVE_EXT_DEFINE_SIDE_EFFECT(flags_side_effect);
97
98 const struct sieve_operand_def flags_side_effect_operand = {
99 .name = "flags operand",
100 .ext_def = &imap4flags_extension,
101 .class = &sieve_side_effect_operand_class,
102 .interface = &ext_side_effects,
103 };
104
105 /*
106 * Tag validation
107 */
108
109 static bool
tag_flags_validate_persistent(struct sieve_validator * valdtr ATTR_UNUSED,struct sieve_command * cmd,const struct sieve_extension * ext)110 tag_flags_validate_persistent(struct sieve_validator *valdtr ATTR_UNUSED,
111 struct sieve_command *cmd,
112 const struct sieve_extension *ext)
113 {
114 if (sieve_command_find_argument(cmd, &tag_flags) == NULL)
115 sieve_command_add_dynamic_tag(cmd, ext, &tag_flags_implicit,
116 -1);
117 return TRUE;
118 }
119
120 static bool
tag_flags_validate(struct sieve_validator * valdtr,struct sieve_ast_argument ** arg,struct sieve_command * cmd)121 tag_flags_validate(struct sieve_validator *valdtr,
122 struct sieve_ast_argument **arg, struct sieve_command *cmd)
123 {
124 struct sieve_ast_argument *tag = *arg;
125
126 /* Detach the tag itself */
127 *arg = sieve_ast_argument_next(*arg);
128
129 /* Check syntax:
130 * :flags <list-of-flags: string-list>
131 */
132 if (!sieve_validate_tag_parameter(valdtr, cmd, tag, *arg, NULL, 0,
133 SAAT_STRING_LIST, FALSE))
134 return FALSE;
135
136 tag->parameters = *arg;
137
138 /* Detach parameter */
139 *arg = sieve_ast_arguments_detach(*arg,1);
140 return TRUE;
141 }
142
143 /*
144 * Code generation
145 */
146
147 static bool
tag_flags_generate(const struct sieve_codegen_env * cgenv,struct sieve_ast_argument * arg,struct sieve_command * cmd)148 tag_flags_generate(const struct sieve_codegen_env *cgenv,
149 struct sieve_ast_argument *arg, struct sieve_command *cmd)
150 {
151 struct sieve_ast_argument *param;
152
153 if (sieve_ast_argument_type(arg) != SAAT_TAG)
154 return FALSE;
155
156 sieve_opr_side_effect_emit(cgenv->sblock, arg->argument->ext,
157 &flags_side_effect);
158
159 if (sieve_argument_is(arg, tag_flags)) {
160 /* Explicit :flags tag */
161 param = arg->parameters;
162
163 /* Call the generation function for the argument */
164 if (param->argument != NULL && param->argument->def != NULL &&
165 param->argument->def->generate != NULL &&
166 !param->argument->def->generate(cgenv, param, cmd))
167 return FALSE;
168 } else if (sieve_argument_is(arg, tag_flags_implicit)) {
169 /* Implicit flags */
170 sieve_opr_omitted_emit(cgenv->sblock);
171 } else {
172 /* Something else?! */
173 i_unreached();
174 }
175 return TRUE;
176 }
177
178 /*
179 * Side effect implementation
180 */
181
182 /* Context data */
183
184 struct seff_flags_context {
185 ARRAY(const char *) keywords;
186 enum mail_flags flags;
187 };
188
189 /* Context coding */
190
191 static bool
seff_flags_dump_context(const struct sieve_side_effect * seffect ATTR_UNUSED,const struct sieve_dumptime_env * denv,sieve_size_t * address)192 seff_flags_dump_context(const struct sieve_side_effect *seffect ATTR_UNUSED,
193 const struct sieve_dumptime_env *denv,
194 sieve_size_t *address)
195 {
196 return sieve_opr_stringlist_dump_ex(denv, address, "flags", "INTERNAL");
197 }
198
199 static struct seff_flags_context *
seff_flags_get_implicit_context(const struct sieve_extension * this_ext,struct sieve_result * result)200 seff_flags_get_implicit_context(const struct sieve_extension *this_ext,
201 struct sieve_result *result)
202 {
203 pool_t pool = sieve_result_pool(result);
204 struct seff_flags_context *ctx;
205 const char *flag;
206 struct ext_imap4flags_iter flit;
207
208 ctx = p_new(pool, struct seff_flags_context, 1);
209 p_array_init(&ctx->keywords, pool, 2);
210
211 T_BEGIN {
212 /* Unpack */
213 ext_imap4flags_get_implicit_flags_init(&flit, this_ext, result);
214 while ((flag = ext_imap4flags_iter_get_flag(&flit)) != NULL) {
215 if (flag != NULL && *flag != '\\') {
216 /* keyword */
217 const char *keyword = p_strdup(pool, flag);
218 array_append(&ctx->keywords, &keyword, 1);
219 } else {
220 /* system flag */
221 if (flag == NULL ||
222 strcasecmp(flag, "\\flagged") == 0)
223 ctx->flags |= MAIL_FLAGGED;
224 else if (strcasecmp(flag, "\\answered") == 0)
225 ctx->flags |= MAIL_ANSWERED;
226 else if (strcasecmp(flag, "\\deleted") == 0)
227 ctx->flags |= MAIL_DELETED;
228 else if (strcasecmp(flag, "\\seen") == 0)
229 ctx->flags |= MAIL_SEEN;
230 else if (strcasecmp(flag, "\\draft") == 0)
231 ctx->flags |= MAIL_DRAFT;
232 }
233 }
234 } T_END;
235
236 return ctx;
237 }
238
239 static int
seff_flags_do_read_context(const struct sieve_side_effect * seffect,const struct sieve_runtime_env * renv,sieve_size_t * address,void ** se_context)240 seff_flags_do_read_context(const struct sieve_side_effect *seffect,
241 const struct sieve_runtime_env *renv,
242 sieve_size_t *address, void **se_context)
243 {
244 pool_t pool = sieve_result_pool(renv->result);
245 struct seff_flags_context *ctx;
246 string_t *flags_item;
247 struct sieve_stringlist *flag_list = NULL;
248 int ret;
249
250 ret = sieve_opr_stringlist_read_ex(renv, address, "flags", TRUE,
251 &flag_list);
252 if (ret <= 0)
253 return ret;
254
255 if (flag_list == NULL) {
256 /* Flag list is omitted, use current value of internal
257 * variable to construct side effect context.
258 */
259 *se_context = seff_flags_get_implicit_context(
260 SIEVE_OBJECT_EXTENSION(seffect), renv->result);
261 return SIEVE_EXEC_OK;
262 }
263
264 ctx = p_new(pool, struct seff_flags_context, 1);
265 p_array_init(&ctx->keywords, pool, 2);
266
267 /* Unpack */
268 flags_item = NULL;
269 while ((ret = sieve_stringlist_next_item(flag_list, &flags_item)) > 0) {
270 const char *flag;
271 struct ext_imap4flags_iter flit;
272
273 ext_imap4flags_iter_init(&flit, flags_item);
274
275 while ((flag = ext_imap4flags_iter_get_flag(&flit)) != NULL) {
276 if (flag != NULL && *flag != '\\') {
277 /* keyword */
278 const char *keyword = p_strdup(pool, flag);
279
280 /* FIXME: should check for duplicates (cannot
281 trust variables) */
282 array_append(&ctx->keywords, &keyword, 1);
283 } else {
284 /* system flag */
285 if (flag == NULL ||
286 strcasecmp(flag, "\\flagged") == 0)
287 ctx->flags |= MAIL_FLAGGED;
288 else if (strcasecmp(flag, "\\answered") == 0)
289 ctx->flags |= MAIL_ANSWERED;
290 else if (strcasecmp(flag, "\\deleted") == 0)
291 ctx->flags |= MAIL_DELETED;
292 else if (strcasecmp(flag, "\\seen") == 0)
293 ctx->flags |= MAIL_SEEN;
294 else if (strcasecmp(flag, "\\draft") == 0)
295 ctx->flags |= MAIL_DRAFT;
296 }
297 }
298 }
299
300 if (ret < 0)
301 return flag_list->exec_status;
302
303 *se_context = (void *)ctx;
304 return SIEVE_EXEC_OK;
305 }
306
307 static int
seff_flags_read_context(const struct sieve_side_effect * seffect,const struct sieve_runtime_env * renv,sieve_size_t * address,void ** se_context)308 seff_flags_read_context(const struct sieve_side_effect *seffect,
309 const struct sieve_runtime_env *renv,
310 sieve_size_t *address, void **se_context)
311 {
312 int ret;
313
314 T_BEGIN {
315 ret = seff_flags_do_read_context(seffect, renv, address,
316 se_context);
317 } T_END;
318
319 return ret;
320 }
321
322 /* Result verification */
323
324 static int
seff_flags_merge(const struct sieve_runtime_env * renv ATTR_UNUSED,const struct sieve_action * action ATTR_UNUSED,const struct sieve_side_effect * old_seffect ATTR_UNUSED,const struct sieve_side_effect * new_seffect,void ** old_context)325 seff_flags_merge(const struct sieve_runtime_env *renv ATTR_UNUSED,
326 const struct sieve_action *action ATTR_UNUSED,
327 const struct sieve_side_effect *old_seffect ATTR_UNUSED,
328 const struct sieve_side_effect *new_seffect,
329 void **old_context)
330 {
331 if (new_seffect != NULL)
332 *old_context = new_seffect->context;
333 return 1;
334 }
335
336 /* Result printing */
337
338 static void
seff_flags_print(const struct sieve_side_effect * seffect,const struct sieve_action * action ATTR_UNUSED,const struct sieve_result_print_env * rpenv,bool * keep ATTR_UNUSED)339 seff_flags_print(const struct sieve_side_effect *seffect,
340 const struct sieve_action *action ATTR_UNUSED,
341 const struct sieve_result_print_env *rpenv,
342 bool *keep ATTR_UNUSED)
343 {
344 struct sieve_result *result = rpenv->result;
345 struct seff_flags_context *ctx =
346 (struct seff_flags_context *)seffect->context;
347 unsigned int i;
348
349 if (ctx == NULL) {
350 ctx = seff_flags_get_implicit_context(
351 SIEVE_OBJECT_EXTENSION(seffect), result);
352 }
353
354 if (ctx->flags != 0 || array_count(&ctx->keywords) > 0) {
355 T_BEGIN {
356 string_t *flags = t_str_new(128);
357
358 if ((ctx->flags & MAIL_FLAGGED) > 0)
359 str_printfa(flags, " \\flagged");
360 if ((ctx->flags & MAIL_ANSWERED) > 0)
361 str_printfa(flags, " \\answered");
362 if ((ctx->flags & MAIL_DELETED) > 0)
363 str_printfa(flags, " \\deleted");
364 if ((ctx->flags & MAIL_SEEN) > 0)
365 str_printfa(flags, " \\seen");
366 if ((ctx->flags & MAIL_DRAFT) > 0)
367 str_printfa(flags, " \\draft");
368
369 for (i = 0; i < array_count(&ctx->keywords); i++) {
370 const char *const *keyword =
371 array_idx(&ctx->keywords, i);
372 str_printfa(flags, " %s",
373 str_sanitize(*keyword, 64));
374 }
375
376 sieve_result_seffect_printf(rpenv, "add IMAP flags:%s",
377 str_c(flags));
378 } T_END;
379 }
380 }
381
382 /* Result execution */
383
384 static int
seff_flags_pre_execute(const struct sieve_side_effect * seffect,const struct sieve_action_exec_env * aenv,void * tr_context,void ** se_tr_context ATTR_UNUSED)385 seff_flags_pre_execute(const struct sieve_side_effect *seffect,
386 const struct sieve_action_exec_env *aenv,
387 void *tr_context, void **se_tr_context ATTR_UNUSED)
388 {
389 struct seff_flags_context *ctx = seffect->context;
390 const char *const *keywords;
391
392 if (ctx == NULL) {
393 ctx = seff_flags_get_implicit_context(
394 SIEVE_OBJECT_EXTENSION(seffect), aenv->result);
395 }
396
397 (void)array_append_space(&ctx->keywords);
398 keywords = array_idx(&ctx->keywords, 0);
399
400 sieve_act_store_add_flags(aenv, tr_context, keywords, ctx->flags);
401 return SIEVE_EXEC_OK;
402 }
403