Coverage Report

Created: 2026-09-03 07:00

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/pigeonhole/src/testsuite/cmd-test-message.c
Line
Count
Source
1
/* Copyright (c) Pigeonhole authors, see top-level COPYING file */
2
3
#include "lib.h"
4
#include "str-sanitize.h"
5
#include "istream.h"
6
#include "mail-storage.h"
7
8
#include "sieve-common.h"
9
#include "sieve-commands.h"
10
#include "sieve-message.h"
11
#include "sieve-actions.h"
12
#include "sieve-validator.h"
13
#include "sieve-generator.h"
14
#include "sieve-interpreter.h"
15
#include "sieve-code.h"
16
#include "sieve-binary.h"
17
#include "sieve-dump.h"
18
19
#include "testsuite-common.h"
20
#include "testsuite-smtp.h"
21
#include "testsuite-mailstore.h"
22
23
/*
24
 * Commands
25
 */
26
27
/* Test_message command
28
 *
29
 * Syntax:
30
 *   test_message ( :smtp / :mailbox <mailbox: string> ) <index: number>
31
 */
32
33
static bool
34
cmd_test_message_registered(struct sieve_validator *valdtr,
35
          const struct sieve_extension *ext,
36
          struct sieve_command_registration *cmd_reg);
37
static bool
38
cmd_test_message_validate(struct sieve_validator *valdtr,
39
        struct sieve_command *cmd);
40
static bool
41
cmd_test_message_generate(const struct sieve_codegen_env *cgenv,
42
        struct sieve_command *ctx);
43
44
const struct sieve_command_def cmd_test_message = {
45
  .identifier = "test_message",
46
  .type = SCT_HYBRID,
47
  .positional_args = 1,
48
  .subtests = 0,
49
  .block_allowed = FALSE,
50
  .block_required = FALSE,
51
  .registered = cmd_test_message_registered,
52
  .validate = cmd_test_message_validate,
53
  .generate = cmd_test_message_generate,
54
};
55
56
/* Test_message_print command
57
 *
58
 * Syntax:
59
 *   test_message_print
60
 */
61
62
static bool
63
cmd_test_message_print_generate(const struct sieve_codegen_env *cgenv,
64
        struct sieve_command *cmd);
65
66
const struct sieve_command_def cmd_test_message_print = {
67
  .identifier = "test_message_print",
68
  .type = SCT_COMMAND,
69
  .positional_args = 0,
70
  .subtests = 0,
71
  .block_allowed = FALSE,
72
  .block_required = FALSE,
73
  .generate = cmd_test_message_print_generate,
74
};
75
76
/*
77
 * Operations
78
 */
79
80
/* Test_message_smtp operation */
81
82
static bool
83
cmd_test_message_smtp_operation_dump(const struct sieve_dumptime_env *denv,
84
             sieve_size_t *address);
85
static int
86
cmd_test_message_smtp_operation_execute(const struct sieve_runtime_env *renv,
87
          sieve_size_t *address);
88
89
const struct sieve_operation_def test_message_smtp_operation = {
90
  .mnemonic = "TEST_MESSAGE_SMTP",
91
  .ext_def = &testsuite_extension,
92
  .code = TESTSUITE_OPERATION_TEST_MESSAGE_SMTP,
93
  .dump = cmd_test_message_smtp_operation_dump,
94
  .execute = cmd_test_message_smtp_operation_execute,
95
};
96
97
/* Test_message_mailbox operation */
98
99
static bool
100
cmd_test_message_mailbox_operation_dump(const struct sieve_dumptime_env *denv,
101
          sieve_size_t *address);
102
static int
103
cmd_test_message_mailbox_operation_execute(const struct sieve_runtime_env *renv,
104
             sieve_size_t *address);
105
106
const struct sieve_operation_def test_message_mailbox_operation = {
107
  .mnemonic = "TEST_MESSAGE_MAILBOX",
108
  .ext_def = &testsuite_extension,
109
  .code = TESTSUITE_OPERATION_TEST_MESSAGE_MAILBOX,
110
  .dump = cmd_test_message_mailbox_operation_dump,
111
  .execute = cmd_test_message_mailbox_operation_execute,
112
};
113
114
/* Test_message_print operation */
115
116
static bool
117
cmd_test_message_print_operation_dump(const struct sieve_dumptime_env *denv,
118
              sieve_size_t *address);
119
static int
120
cmd_test_message_print_operation_execute(const struct sieve_runtime_env *renv,
121
           sieve_size_t *address);
122
123
const struct sieve_operation_def test_message_print_operation = {
124
  .mnemonic = "TEST_MESSAGE_PRINT",
125
  .ext_def = &testsuite_extension,
126
  .code = TESTSUITE_OPERATION_TEST_MESSAGE_PRINT,
127
  .dump = cmd_test_message_print_operation_dump,
128
  .execute = cmd_test_message_print_operation_execute,
129
};
130
131
/*
132
 * Compiler context data
133
 */
134
135
enum test_message_source {
136
  MSG_SOURCE_SMTP,
137
  MSG_SOURCE_MAILBOX,
138
  MSG_SOURCE_LAST,
139
};
140
141
const struct sieve_operation_def *test_message_operations[] = {
142
  &test_message_smtp_operation,
143
  &test_message_mailbox_operation,
144
};
145
146
struct cmd_test_message_context_data {
147
  enum test_message_source msg_source;
148
  const char *folder;
149
};
150
151
#define CMD_TEST_MESSAGE_ERROR_DUP_TAG \
152
  "exactly one of the ':smtp' or ':folder' tags must be specified " \
153
  "for the test_message command, but more were found"
154
155
/*
156
 * Command tags
157
 */
158
159
static bool
160
cmd_test_message_validate_smtp_tag(struct sieve_validator *valdtr,
161
           struct sieve_ast_argument **arg,
162
           struct sieve_command *cmd);
163
static bool
164
cmd_test_message_validate_folder_tag(struct sieve_validator *valdtr,
165
             struct sieve_ast_argument **arg,
166
             struct sieve_command *cmd);
167
168
static const struct sieve_argument_def test_message_smtp_tag = {
169
  .identifier = "smtp",
170
  .validate = cmd_test_message_validate_smtp_tag,
171
};
172
173
static const struct sieve_argument_def test_message_folder_tag = {
174
  .identifier = "folder",
175
  .validate = cmd_test_message_validate_folder_tag,
176
};
177
178
static bool
179
cmd_test_message_registered(struct sieve_validator *valdtr,
180
          const struct sieve_extension *ext,
181
          struct sieve_command_registration *cmd_reg)
182
0
{
183
  /* Register our tags */
184
0
  sieve_validator_register_tag(valdtr, cmd_reg, ext,
185
0
             &test_message_folder_tag, 0);
186
0
  sieve_validator_register_tag(valdtr, cmd_reg, ext,
187
0
             &test_message_smtp_tag, 0);
188
0
  return TRUE;
189
0
}
190
191
static struct cmd_test_message_context_data *
192
cmd_test_message_validate_tag(struct sieve_validator *valdtr,
193
            struct sieve_ast_argument **arg,
194
            struct sieve_command *cmd)
195
0
{
196
0
  struct cmd_test_message_context_data *ctx_data =
197
0
    (struct cmd_test_message_context_data *)cmd->data;
198
199
0
  if (ctx_data != NULL) {
200
0
    sieve_argument_validate_error(valdtr, *arg,
201
0
                CMD_TEST_MESSAGE_ERROR_DUP_TAG);
202
0
    return NULL;
203
0
  }
204
205
0
  ctx_data = p_new(sieve_command_pool(cmd),
206
0
       struct cmd_test_message_context_data, 1);
207
0
  cmd->data = ctx_data;
208
209
  /* Delete this tag */
210
0
  *arg = sieve_ast_arguments_detach(*arg, 1);
211
212
0
  return ctx_data;
213
0
}
214
215
static bool
216
cmd_test_message_validate_smtp_tag(struct sieve_validator *valdtr,
217
           struct sieve_ast_argument **arg,
218
           struct sieve_command *cmd)
219
0
{
220
0
  struct cmd_test_message_context_data *ctx_data =
221
0
    cmd_test_message_validate_tag(valdtr, arg, cmd);
222
223
  /* Return value is NULL on error */
224
0
  if (ctx_data == NULL)
225
0
    return FALSE;
226
227
  /* Assign chosen message source */
228
0
  ctx_data->msg_source = MSG_SOURCE_SMTP;
229
230
0
  return TRUE;
231
0
}
232
233
static bool
234
cmd_test_message_validate_folder_tag(struct sieve_validator *valdtr,
235
             struct sieve_ast_argument **arg,
236
             struct sieve_command *cmd)
237
0
{
238
0
  struct sieve_ast_argument *tag = *arg;
239
0
  struct cmd_test_message_context_data *ctx_data =
240
0
    cmd_test_message_validate_tag(valdtr, arg, cmd);
241
242
  /* Return value is NULL on error */
243
0
  if (ctx_data == NULL)
244
0
    return FALSE;
245
246
  /* Assign chose message source */
247
0
  ctx_data->msg_source = MSG_SOURCE_MAILBOX;
248
249
  /* Check syntax:
250
   *   :folder string
251
   */
252
0
  if (!sieve_validate_tag_parameter(valdtr, cmd, tag, *arg, NULL, 0,
253
0
            SAAT_STRING, FALSE)) {
254
0
    return FALSE;
255
0
  }
256
257
  /* Check name validity when folder argument is not a variable */
258
0
  if ( sieve_argument_is_string_literal(*arg) ) {
259
0
    const char *folder = sieve_ast_argument_strc(*arg), *error;
260
261
0
    if ( !sieve_mailbox_check_name(folder, &error) ) {
262
0
      sieve_command_validate_error(
263
0
        valdtr, cmd, "%s command: "
264
0
        "invalid mailbox '%s' specified: %s",
265
0
        sieve_command_identifier(cmd),
266
0
        str_sanitize(folder, 256), error);
267
0
      return FALSE;
268
0
    }
269
0
  }
270
271
  /* Skip parameter */
272
0
  *arg = sieve_ast_argument_next(*arg);
273
274
0
  return TRUE;
275
0
}
276
277
/*
278
 * Validation
279
 */
280
281
static bool
282
cmd_test_message_validate(struct sieve_validator *valdtr,
283
        struct sieve_command *cmd)
284
0
{
285
0
  struct sieve_ast_argument *arg = cmd->first_positional;
286
287
0
  if (cmd->data == NULL) {
288
0
    sieve_command_validate_error(
289
0
      valdtr, cmd,
290
0
      "the test_message command requires either "
291
0
      "the :smtp or the :mailbox tag to be specified");
292
0
    return FALSE;
293
0
  }
294
295
0
  if (!sieve_validate_positional_argument(valdtr, cmd, arg, "index", 1,
296
0
            SAAT_NUMBER))
297
0
    return FALSE;
298
299
0
  return sieve_validator_argument_activate(valdtr, cmd, arg, FALSE);
300
0
}
301
302
/*
303
 * Code generation
304
 */
305
306
static bool
307
cmd_test_message_generate(const struct sieve_codegen_env *cgenv,
308
        struct sieve_command *cmd)
309
0
{
310
0
  struct cmd_test_message_context_data *ctx_data =
311
0
    (struct cmd_test_message_context_data *)cmd->data;
312
313
0
  i_assert(ctx_data->msg_source < MSG_SOURCE_LAST);
314
315
  /* Emit operation */
316
0
  sieve_operation_emit(cgenv->sblock, cmd->ext,
317
0
           test_message_operations[ctx_data->msg_source]);
318
319
  /* Emit is_test flag */
320
0
  sieve_binary_emit_byte(cgenv->sblock,
321
0
             (cmd->ast_node->type == SAT_TEST ? 1 : 0));
322
323
  /* Generate arguments */
324
0
  if (!sieve_generate_arguments(cgenv, cmd, NULL))
325
0
    return FALSE;
326
327
0
  return TRUE;
328
0
}
329
330
static bool
331
cmd_test_message_print_generate(const struct sieve_codegen_env *cgenv,
332
        struct sieve_command *cmd)
333
0
{
334
  /* Emit operation */
335
0
  sieve_operation_emit(cgenv->sblock, cmd->ext,
336
0
           &test_message_print_operation);
337
0
  return TRUE;
338
0
}
339
340
/*
341
 * Code dump
342
 */
343
344
static bool
345
cmd_test_message_smtp_operation_dump(const struct sieve_dumptime_env *denv,
346
             sieve_size_t *address)
347
0
{
348
0
  unsigned int is_test;
349
350
0
  if (!sieve_binary_read_byte(denv->sblock, address, &is_test))
351
0
    return FALSE;
352
353
0
  sieve_code_dumpf(denv, "TEST_MESSAGE_SMTP (%s):",
354
0
       (is_test > 0 ? "TEST" : "COMMAND"));
355
356
0
  sieve_code_descend(denv);
357
358
0
  return sieve_opr_number_dump(denv, address, "index");
359
0
}
360
361
static bool
362
cmd_test_message_mailbox_operation_dump(const struct sieve_dumptime_env *denv,
363
          sieve_size_t *address)
364
0
{
365
0
  unsigned int is_test;
366
367
0
  if (!sieve_binary_read_byte(denv->sblock, address, &is_test))
368
0
    return FALSE;
369
370
0
  sieve_code_dumpf(denv, "TEST_MESSAGE_MAILBOX (%s):",
371
0
       (is_test > 0 ? "TEST" : "COMMAND"));
372
373
0
  sieve_code_descend(denv);
374
375
0
  return (sieve_opr_string_dump(denv, address, "folder") &&
376
0
    sieve_opr_number_dump(denv, address, "index"));
377
0
}
378
379
static bool
380
cmd_test_message_print_operation_dump(const struct sieve_dumptime_env *denv,
381
              sieve_size_t *address ATTR_UNUSED)
382
0
{
383
0
  sieve_code_dumpf(denv, "TEST_MESSAGE_PRINT");
384
385
0
  return TRUE;
386
0
}
387
388
/*
389
 * Intepretation
390
 */
391
392
static int
393
cmd_test_message_smtp_operation_execute(const struct sieve_runtime_env *renv,
394
          sieve_size_t *address)
395
0
{
396
0
  sieve_number_t msg_index;
397
0
  unsigned int is_test = 0;
398
0
  bool result;
399
0
  int ret;
400
401
  /*
402
   * Read operands
403
   */
404
405
  /* Is test */
406
407
0
  if (!sieve_binary_read_byte(renv->sblock, address, &is_test)) {
408
0
    sieve_runtime_trace_error(renv, "invalid is_test flag");
409
0
    return SIEVE_EXEC_BIN_CORRUPT;
410
0
  }
411
412
  /* Index */
413
414
0
  ret = sieve_opr_number_read(renv, address, "index", &msg_index);
415
0
  if (ret <= 0)
416
0
    return ret;
417
418
  /*
419
   * Perform operation
420
   */
421
422
0
  if (is_test > 0) {
423
0
    if (sieve_runtime_trace_active(renv, SIEVE_TRLVL_TESTS)) {
424
0
      sieve_runtime_trace(
425
0
        renv, 0, "testsuite: test_message test");
426
0
      sieve_runtime_trace_descend(renv);
427
0
      sieve_runtime_trace(
428
0
        renv, 0,
429
0
        "check and retrieve smtp message [index=%llu]",
430
0
        (unsigned long long)msg_index);
431
0
    }
432
0
  } else {
433
0
    if (sieve_runtime_trace_active(renv, SIEVE_TRLVL_COMMANDS)) {
434
0
      sieve_runtime_trace(
435
0
        renv, 0, "testsuite: test_message command");
436
0
      sieve_runtime_trace_descend(renv);
437
0
      sieve_runtime_trace(
438
0
        renv, 0,
439
0
        "retrieve smtp message [index=%llu]",
440
0
        (unsigned long long)msg_index);
441
0
    }
442
0
  }
443
444
0
  result = testsuite_smtp_get(renv, msg_index);
445
446
0
  if (is_test > 0) {
447
0
    sieve_interpreter_set_test_result(renv->interp, result);
448
0
    return SIEVE_EXEC_OK;
449
0
  }
450
451
0
  if (!result) {
452
0
    return testsuite_test_failf(
453
0
      renv, "no outgoing SMTP message with index %llu",
454
0
      (unsigned long long)msg_index);
455
0
  }
456
457
0
  return SIEVE_EXEC_OK;
458
0
}
459
460
static int
461
cmd_test_message_mailbox_operation_execute(const struct sieve_runtime_env *renv,
462
             sieve_size_t *address)
463
0
{
464
0
  string_t *folder;
465
0
  sieve_number_t msg_index;
466
0
  unsigned int is_test = 0;
467
0
  bool result;
468
0
  const char *error;
469
0
  int ret;
470
471
  /*
472
   * Read operands
473
   */
474
475
  /* Is test */
476
0
  if (!sieve_binary_read_byte(renv->sblock, address, &is_test)) {
477
0
    sieve_runtime_trace_error(renv, "invalid is_test flag");
478
0
    return SIEVE_EXEC_BIN_CORRUPT;
479
0
  }
480
481
  /* Folder */
482
0
  ret = sieve_opr_string_read(renv, address, "folder", &folder);
483
0
  if (ret <= 0)
484
0
    return ret;
485
486
  /* Index */
487
0
  ret = sieve_opr_number_read(renv, address, "index", &msg_index);
488
0
  if (ret <= 0)
489
0
    return ret;
490
491
0
  if (!sieve_mailbox_check_name(str_c(folder), &error)) {
492
0
    return testsuite_test_failf(
493
0
      renv, "invalid mailbox '%s' specified: %s",
494
0
      str_c(folder), error);
495
0
  }
496
497
  /*
498
   * Perform operation
499
   */
500
501
0
  if (is_test > 0) {
502
0
    if (sieve_runtime_trace_active(renv, SIEVE_TRLVL_TESTS)) {
503
0
      sieve_runtime_trace(
504
0
        renv, 0, "testsuite: test_message test");
505
0
      sieve_runtime_trace_descend(renv);
506
0
      sieve_runtime_trace(
507
0
        renv, 0, "check and retrieve mailbox message "
508
0
        "[mailbox='%s' index=%llu]",
509
0
        str_c(folder), (unsigned long long)msg_index);
510
0
    }
511
0
  } else {
512
0
    if (sieve_runtime_trace_active(renv, SIEVE_TRLVL_COMMANDS)) {
513
0
      sieve_runtime_trace(
514
0
        renv, 0, "testsuite: test_message command");
515
0
      sieve_runtime_trace_descend(renv);
516
0
      sieve_runtime_trace(
517
0
        renv, 0, "retrieve mailbox message "
518
0
        "[mailbox='%s' index=%llu]",
519
0
        str_c(folder), (unsigned long long)msg_index);
520
0
    }
521
0
  }
522
523
0
  result = testsuite_mailstore_mail_index(renv, str_c(folder), msg_index);
524
525
0
  if (is_test > 0) {
526
0
    sieve_interpreter_set_test_result(renv->interp, result);
527
0
    return SIEVE_EXEC_OK;
528
0
  }
529
530
0
  if (!result) {
531
0
    return testsuite_test_failf(
532
0
      renv, "no message in folder '%s' with index %llu",
533
0
      str_c(folder), (unsigned long long)msg_index);
534
0
  }
535
536
0
  return SIEVE_EXEC_OK;
537
0
}
538
539
static int
540
cmd_test_message_print_operation_execute(const struct sieve_runtime_env *renv,
541
           sieve_size_t *address ATTR_UNUSED)
542
0
{
543
0
  struct mail *mail = sieve_message_get_mail(renv->msgctx);
544
0
  struct istream *input;
545
0
  const unsigned char *data;
546
0
  size_t size;
547
548
0
  if (testsuite_silent)
549
0
    return SIEVE_EXEC_OK;
550
551
0
  if (mail_get_stream(mail, NULL, NULL, &input) < 0) {
552
0
    sieve_runtime_error(renv, NULL,  "test_message_print: "
553
0
            "failed to read current message");
554
0
    return SIEVE_EXEC_OK;
555
0
  }
556
557
0
  printf("\n--MESSAGE: \n");
558
559
  /* Pipe the message to the outgoing SMTP transport */
560
0
  while (i_stream_read_more(input, &data, &size) > 0) {
561
0
    ssize_t wret;
562
563
0
    wret = write(1, data, size);
564
0
    if (wret <= 0)
565
0
      break;
566
0
    i_stream_skip(input, wret);
567
0
  }
568
0
  printf("\n--MESSAGE--\n");
569
570
0
  return SIEVE_EXEC_OK;
571
0
}