1/* Unit test for sieve */
2/* Heavily based on the old sieve/test.c which bore this message:
3 *
4 * - * test.c -- tester for libcyrus_sieve
5 * - * Larry Greenfield
6 *
7 */
8#include "cunit/cunit.h"
9#include <malloc.h>
10#include "sieve/bytecode.h"
11#include "sieve/comparator.h"
12#include "sieve/message.h"
13#include "sieve/sieve_interface.h"
14#include "sieve/variables.h"
15#include "imap/message.h"
16#include "prot.h"
17#include "retry.h"
18#include "imap/spool.h"
19#include "map.h"
20#include "util.h"
21#include "cyrusdb.h"
22#include "libcyr_cfg.h"
23#include "libconfig.h"
24#include "xstrlcat.h"
25#include "xstrlcpy.h"
26#include "xmalloc.h"
27
28#define DBDIR       "test-sieve-dbdir"
29#define PARTITION   "default"
30
31typedef struct {
32    sieve_interp_t *interp;
33    sieve_execute_t *exe;
34    struct {
35        unsigned int actions;
36        unsigned int errors;
37        unsigned int redirects;
38        unsigned int discards;
39        unsigned int rejects;
40        unsigned int fileintos;
41        unsigned int keeps;
42        unsigned int notifies;
43        unsigned int vaction_responses;
44    } stats;
45    char *redirected_to;
46    char *reject_message;
47    char *filed_mailbox;
48    char *filed_flags;
49    char *notify_method;
50    char *notify_priority;
51    char *notify_options;
52    char *vacation_message;
53    char *vacation_subject;
54    char *vacation_to;
55    char *vacation_from;
56    strarray_t *compile_errors;
57    strarray_t *run_errors;
58    strarray_t *flags;
59} sieve_test_context_t;
60
61typedef struct {
62    const char *text;
63    int length;
64    struct message_content content;
65    hdrcache_t headers;
66    char *filename;
67} sieve_test_message_t;
68
69/* set to SIEVE_DONE if you want to test "already responded */
70int autorespond_reponse = SIEVE_OK;
71
72extern int verbose;
73
74static void test_variable_modifiers(void)
75{
76    /*
77   Examples:
78      # The value assigned to the variable is printed after the arrow
79      set "a" "juMBlEd lETteRS";             => "juMBlEd lETteRS"
80      set :length "b" "${a}";                => "15"
81      set :lower "b" "${a}";                 => "jumbled letters"
82      set :upperfirst "b" "${a}";            => "JuMBlEd lETteRS"
83      set :upperfirst :lower "b" "${a}";     => "Jumbled letters"
84      set :quotewildcard "b" "Rock*";        => "Rock\*"
85     */
86    const char *a = "juMBlEd lETteRS";
87    char *b;
88    CU_ASSERT_STRING_EQUAL(a, "juMBlEd lETteRS");
89    b = variables_modify_string(a, BFV_LENGTH);
90    CU_ASSERT_STRING_EQUAL(b, "15");
91    free(b);
92    b = variables_modify_string(a, BFV_LOWER);
93    CU_ASSERT_STRING_EQUAL(b, "jumbled letters");
94    free(b);
95    b = variables_modify_string(a, BFV_UPPERFIRST);
96    CU_ASSERT_STRING_EQUAL(b, "JuMBlEd lETteRS");
97    free(b);
98    b = variables_modify_string(a, BFV_UPPERFIRST | BFV_LOWER);
99    CU_ASSERT_STRING_EQUAL(b, "Jumbled letters");
100    free(b);
101    b = variables_modify_string("Rock*", BFV_QUOTEWILDCARD);
102    CU_ASSERT_STRING_EQUAL(b, "Rock\\*");
103    free(b);
104    b = variables_modify_string("Rock\\*", BFV_QUOTEWILDCARD | BFV_ENCODEURL);
105    CU_ASSERT_STRING_EQUAL(b, "Rock%5C%5C%5C%2A");
106    free(b);
107}
108
109#define TESTCASE(_comp, _mode, _pat, _text, _result)            \
110    comprock = NULL;                                            \
111    c = lookup_comp(_comp, _mode, -1, &comprock);               \
112    CU_ASSERT_PTR_NOT_NULL(c);                                  \
113    if (c) {                                                    \
114        res = c(_text, strlen(_text), _pat, comprock);          \
115        CU_ASSERT_EQUAL(res, _result);                          \
116    }
117
118static void test_comparator(void)
119{
120    void *comprock;
121    comparator_t *c;
122    int res;
123
124    TESTCASE( B_OCTET, B_IS, "", "", 1 );
125    TESTCASE( B_OCTET, B_IS, "a", "", 0 );
126    TESTCASE( B_OCTET, B_IS, "", "a", 0 );
127    TESTCASE( B_OCTET, B_IS, "a", "a", 1 );
128    TESTCASE( B_OCTET, B_IS, "a", "A", 0 );
129
130    TESTCASE( B_ASCIICASEMAP, B_IS, "", "", 1 );
131    TESTCASE( B_ASCIICASEMAP, B_IS, "a", "", 0 );
132    TESTCASE( B_ASCIICASEMAP, B_IS, "", "a", 0 );
133    TESTCASE( B_ASCIICASEMAP, B_IS, "a", "a", 1 );
134    TESTCASE( B_ASCIICASEMAP, B_IS, "a", "A", 1 );
135
136    TESTCASE( B_ASCIINUMERIC, B_IS, "123", "123", 1 );
137    TESTCASE( B_ASCIINUMERIC, B_IS, "123", "-123", 0 );
138    TESTCASE( B_ASCIINUMERIC, B_IS, "abc", "123", 0 );
139    TESTCASE( B_ASCIINUMERIC, B_IS, "abc", "abc", 1 );
140    TESTCASE( B_ASCIINUMERIC, B_IS, "12345678900", "3755744308", 0 );    /* test for 32bit overflow */
141    TESTCASE( B_ASCIINUMERIC, B_IS, "1567", "1567pounds", 1 );
142    TESTCASE( B_ASCIINUMERIC, B_IS, "", "", 1 );
143    TESTCASE( B_ASCIINUMERIC, B_IS, "123456789", "567", 0 );
144    TESTCASE( B_ASCIINUMERIC, B_IS, "567", "123456789", 0 );
145    TESTCASE( B_ASCIINUMERIC, B_IS, "123456789", "00000123456789", 1 );
146    TESTCASE( B_ASCIINUMERIC, B_IS, "102", "1024", 0 );
147    TESTCASE( B_ASCIINUMERIC, B_IS, "1567M", "1567 arg", 1 );
148
149    TESTCASE( B_OCTET, B_CONTAINS, "", "", 1 );
150    TESTCASE( B_OCTET, B_CONTAINS, "", "a", 1 );
151    TESTCASE( B_OCTET, B_CONTAINS, "a", "", 0 );
152    TESTCASE( B_OCTET, B_CONTAINS, "a", "a", 1 );
153    TESTCASE( B_OCTET, B_CONTAINS, "a", "ab", 1 );
154    TESTCASE( B_OCTET, B_CONTAINS, "a", "ba", 1 );
155    TESTCASE( B_OCTET, B_CONTAINS, "a", "aba", 1 );
156    TESTCASE( B_OCTET, B_CONTAINS, "a", "bab", 1 );
157    TESTCASE( B_OCTET, B_CONTAINS, "a", "bb", 0 );
158    TESTCASE( B_OCTET, B_CONTAINS, "a", "bbb", 0 );
159
160    TESTCASE( B_OCTET, B_MATCHES, "", "", 1 );
161    TESTCASE( B_OCTET, B_MATCHES, "", "a", 0 );
162    TESTCASE( B_OCTET, B_MATCHES, "a", "", 0 );
163    TESTCASE( B_OCTET, B_MATCHES, "a", "a", 1 );
164    TESTCASE( B_OCTET, B_MATCHES, "a", "ab", 0 );
165    TESTCASE( B_OCTET, B_MATCHES, "a", "ba", 0 );
166    TESTCASE( B_OCTET, B_MATCHES, "a", "aba", 0 );
167    TESTCASE( B_OCTET, B_MATCHES, "a", "bab", 0 );
168    TESTCASE( B_OCTET, B_MATCHES, "a", "bb", 0 );
169    TESTCASE( B_OCTET, B_MATCHES, "a", "bbb", 0 );
170
171    TESTCASE( B_OCTET, B_MATCHES, "*", "", 1 );
172    TESTCASE( B_OCTET, B_MATCHES, "*", "a", 1 );
173    TESTCASE( B_OCTET, B_MATCHES, "*a*", "", 0 );
174    TESTCASE( B_OCTET, B_MATCHES, "*a*", "a", 1 );
175    TESTCASE( B_OCTET, B_MATCHES, "*a*", "ab", 1 );
176    TESTCASE( B_OCTET, B_MATCHES, "*a*", "ba", 1 );
177    TESTCASE( B_OCTET, B_MATCHES, "*a*", "aba", 1 );
178    TESTCASE( B_OCTET, B_MATCHES, "*a*", "bab", 1 );
179    TESTCASE( B_OCTET, B_MATCHES, "*a*", "bb", 0 );
180    TESTCASE( B_OCTET, B_MATCHES, "*a*", "bbb", 0 );
181
182    TESTCASE( B_OCTET, B_MATCHES, "*a", "", 0 );
183    TESTCASE( B_OCTET, B_MATCHES, "*a", "a", 1 );
184    TESTCASE( B_OCTET, B_MATCHES, "*a", "ab", 0 );
185    TESTCASE( B_OCTET, B_MATCHES, "*a", "ba", 1 );
186    TESTCASE( B_OCTET, B_MATCHES, "*a", "aba", 1 );
187    TESTCASE( B_OCTET, B_MATCHES, "*a", "bab", 0 );
188    TESTCASE( B_OCTET, B_MATCHES, "*a", "bb", 0 );
189    TESTCASE( B_OCTET, B_MATCHES, "*a", "bbb", 0 );
190
191    TESTCASE( B_OCTET, B_MATCHES, "a*", "", 0 );
192    TESTCASE( B_OCTET, B_MATCHES, "a*", "a", 1 );
193    TESTCASE( B_OCTET, B_MATCHES, "a*", "ab", 1 );
194    TESTCASE( B_OCTET, B_MATCHES, "a*", "ba", 0 );
195    TESTCASE( B_OCTET, B_MATCHES, "a*", "aba", 1 );
196    TESTCASE( B_OCTET, B_MATCHES, "a*", "bab", 0 );
197    TESTCASE( B_OCTET, B_MATCHES, "a*", "bb", 0 );
198    TESTCASE( B_OCTET, B_MATCHES, "a*", "bbb", 0 );
199
200    TESTCASE( B_OCTET, B_MATCHES, "a*b", "", 0 );
201    TESTCASE( B_OCTET, B_MATCHES, "a*b", "a", 0 );
202    TESTCASE( B_OCTET, B_MATCHES, "a*b", "ab", 1 );
203    TESTCASE( B_OCTET, B_MATCHES, "a*b", "ba", 0 );
204    TESTCASE( B_OCTET, B_MATCHES, "a*b", "aba", 0 );
205    TESTCASE( B_OCTET, B_MATCHES, "a*b", "bab", 0 );
206    TESTCASE( B_OCTET, B_MATCHES, "a*b", "bb", 0 );
207    TESTCASE( B_OCTET, B_MATCHES, "a*b", "bbb", 0 );
208    TESTCASE( B_OCTET, B_MATCHES, "a*b", "abbb", 1 );
209    TESTCASE( B_OCTET, B_MATCHES, "a*b", "acb", 1 );
210    TESTCASE( B_OCTET, B_MATCHES, "a*b", "acbc", 0 );
211
212    TESTCASE( B_OCTET, B_MATCHES, "a?b", "", 0 );
213    TESTCASE( B_OCTET, B_MATCHES, "a?b", "a", 0 );
214    TESTCASE( B_OCTET, B_MATCHES, "a?b", "ab", 0 );
215    TESTCASE( B_OCTET, B_MATCHES, "a?b", "ba", 0 );
216    TESTCASE( B_OCTET, B_MATCHES, "a?b", "aba", 0 );
217    TESTCASE( B_OCTET, B_MATCHES, "a?b", "bab", 0 );
218    TESTCASE( B_OCTET, B_MATCHES, "a?b", "bb", 0 );
219    TESTCASE( B_OCTET, B_MATCHES, "a?b", "bbb", 0 );
220    TESTCASE( B_OCTET, B_MATCHES, "a?b", "abbb", 0 );
221    TESTCASE( B_OCTET, B_MATCHES, "a?b", "acb", 1 );
222    TESTCASE( B_OCTET, B_MATCHES, "a?b", "acbc", 0 );
223
224    TESTCASE( B_OCTET, B_MATCHES, "a*?b", "", 0 );
225    TESTCASE( B_OCTET, B_MATCHES, "a*?b", "a", 0 );
226    TESTCASE( B_OCTET, B_MATCHES, "a*?b", "ab", 0 );
227    TESTCASE( B_OCTET, B_MATCHES, "a*?b", "ba", 0 );
228    TESTCASE( B_OCTET, B_MATCHES, "a*?b", "aba", 0 );
229    TESTCASE( B_OCTET, B_MATCHES, "a*?b", "bab", 0 );
230    TESTCASE( B_OCTET, B_MATCHES, "a*?b", "bb", 0 );
231    TESTCASE( B_OCTET, B_MATCHES, "a*?b", "bbb", 0 );
232    TESTCASE( B_OCTET, B_MATCHES, "a*?b", "abbb", 1 );
233    TESTCASE( B_OCTET, B_MATCHES, "a*?b", "acb", 1 );
234    TESTCASE( B_OCTET, B_MATCHES, "a*?b", "acbc", 0 );
235
236    TESTCASE( B_OCTET, B_MATCHES, "a?*b", "", 0 );
237    TESTCASE( B_OCTET, B_MATCHES, "a?*b", "a", 0 );
238    TESTCASE( B_OCTET, B_MATCHES, "a?*b", "ab", 0 );
239    TESTCASE( B_OCTET, B_MATCHES, "a?*b", "ba", 0 );
240    TESTCASE( B_OCTET, B_MATCHES, "a?*b", "aba", 0 );
241    TESTCASE( B_OCTET, B_MATCHES, "a?*b", "bab", 0 );
242    TESTCASE( B_OCTET, B_MATCHES, "a?*b", "bb", 0 );
243    TESTCASE( B_OCTET, B_MATCHES, "a?*b", "bbb", 0 );
244    TESTCASE( B_OCTET, B_MATCHES, "a?*b", "abbb", 1 );
245    TESTCASE( B_OCTET, B_MATCHES, "a?*b", "acb", 1 );
246    TESTCASE( B_OCTET, B_MATCHES, "a?*b", "acbc", 0 );
247
248    TESTCASE( B_OCTET, B_MATCHES, "a*?*b", "", 0 );
249    TESTCASE( B_OCTET, B_MATCHES, "a*?*b", "a", 0 );
250    TESTCASE( B_OCTET, B_MATCHES, "a*?*b", "ab", 0 );
251    TESTCASE( B_OCTET, B_MATCHES, "a*?*b", "ba", 0 );
252    TESTCASE( B_OCTET, B_MATCHES, "a*?*b", "aba", 0 );
253    TESTCASE( B_OCTET, B_MATCHES, "a*?*b", "bab", 0 );
254    TESTCASE( B_OCTET, B_MATCHES, "a*?*b", "bb", 0 );
255    TESTCASE( B_OCTET, B_MATCHES, "a*?*b", "bbb", 0 );
256    TESTCASE( B_OCTET, B_MATCHES, "a*?*b", "abbb", 1 );
257    TESTCASE( B_OCTET, B_MATCHES, "a*?*b", "acb", 1 );
258    TESTCASE( B_OCTET, B_MATCHES, "a*?*b?", "acbc", 1 );
259
260    TESTCASE( B_ASCIICASEMAP, B_MATCHES, "a*b", "", 0 );
261    TESTCASE( B_ASCIICASEMAP, B_MATCHES, "a*b", "a", 0 );
262    TESTCASE( B_ASCIICASEMAP, B_MATCHES, "a*b", "ab", 1 );
263    TESTCASE( B_ASCIICASEMAP, B_MATCHES, "a*b", "ba", 0 );
264    TESTCASE( B_ASCIICASEMAP, B_MATCHES, "a*b", "aba", 0 );
265    TESTCASE( B_ASCIICASEMAP, B_MATCHES, "a*b", "bab", 0 );
266    TESTCASE( B_ASCIICASEMAP, B_MATCHES, "a*b", "bb", 0 );
267    TESTCASE( B_ASCIICASEMAP, B_MATCHES, "a*b", "bbb", 0 );
268    TESTCASE( B_ASCIICASEMAP, B_MATCHES, "a*b", "abbb", 1 );
269    TESTCASE( B_ASCIICASEMAP, B_MATCHES, "a*b", "acb", 1 );
270    TESTCASE( B_ASCIICASEMAP, B_MATCHES, "a*b", "acbc", 0 );
271
272    TESTCASE( B_ASCIICASEMAP, B_MATCHES, "a*b", "", 0 );
273    TESTCASE( B_ASCIICASEMAP, B_MATCHES, "a*b", "A", 0 );
274    TESTCASE( B_ASCIICASEMAP, B_MATCHES, "a*b", "Ab", 1 );
275    TESTCASE( B_ASCIICASEMAP, B_MATCHES, "a*b", "BA", 0 );
276    TESTCASE( B_ASCIICASEMAP, B_MATCHES, "a*b", "ABA", 0 );
277    TESTCASE( B_ASCIICASEMAP, B_MATCHES, "a*b", "BAb", 0 );
278    TESTCASE( B_ASCIICASEMAP, B_MATCHES, "a*b", "BB", 0 );
279    TESTCASE( B_ASCIICASEMAP, B_MATCHES, "a*b", "BBB", 0 );
280    TESTCASE( B_ASCIICASEMAP, B_MATCHES, "a*b", "aBBB", 1 );
281    TESTCASE( B_ASCIICASEMAP, B_MATCHES, "a*b", "ACB", 1 );
282    TESTCASE( B_ASCIICASEMAP, B_MATCHES, "a*b", "ACBC", 0 );
283}
284
285
286/* gets the header "head" from msg. */
287static int getheader(void *mc, const char *name, const char ***body)
288{
289    sieve_test_message_t *msg = (sieve_test_message_t *)mc;
290
291    *body = spool_getheader(msg->headers, name);
292    if (!*body)
293        return SIEVE_FAIL;
294    return SIEVE_OK;
295}
296
297static int getsize(void *mc, int *size)
298{
299    sieve_test_message_t *msg = (sieve_test_message_t *)mc;
300
301    *size = msg->length;
302    return SIEVE_OK;
303}
304
305static int getbody(void *mc, const char **content_types, sieve_bodypart_t ***parts)
306{
307    sieve_test_message_t *msg = (sieve_test_message_t *)mc;
308    int r = 0;
309
310    if (!msg->content.body) {
311        /* parse the message body if we haven't already */
312        FILE *fp = fopen(msg->filename, "r");
313        CU_ASSERT_PTR_NOT_NULL(fp);
314        r = message_parse_file(fp,
315                               &msg->content.base,
316                               &msg->content.len,
317                               &msg->content.body);
318        CU_ASSERT_EQUAL(r, 0);
319        fclose(fp);
320    }
321
322    /* XXX currently struct bodypart as defined in message.h is the same as
323       sieve_bodypart_t as defined in sieve_interface.h, so we can typecast */
324    if (!r)
325        message_fetch_part(&msg->content, content_types,
326                           (struct bodypart ***) parts);
327
328    if (r)
329        return SIEVE_FAIL;
330    return SIEVE_OK;
331}
332
333static int getinclude(void *sc __attribute__((unused)),
334                      const char *script,
335                      int isglobal __attribute__((unused)),
336                      char *fpath, size_t size)
337{
338    strlcpy(fpath, script, size);
339    strlcat(fpath, ".bc", size);
340    return SIEVE_OK;
341}
342
343static int redirect(void *ac, void *ic, void *sc __attribute__((unused)),
344                    void *mc __attribute__((unused)),
345                    const char **errmsg __attribute__((unused)))
346{
347    sieve_redirect_context_t *rc = (sieve_redirect_context_t *)ac;
348    sieve_test_context_t *ctx = (sieve_test_context_t *)ic;
349
350    ctx->stats.actions++;
351    ctx->stats.redirects++;
352    free(ctx->redirected_to);
353    ctx->redirected_to = xstrdup(rc->addr);
354
355    /* TODO: test returning SIEVE_FAIL */
356    return SIEVE_OK;
357}
358
359static int discard(void *ac __attribute__((unused)),
360                   void *ic, void *sc __attribute__((unused)),
361                   void *mc __attribute__((unused)),
362                   const char **errmsg __attribute__((unused)))
363{
364    sieve_test_context_t *ctx = (sieve_test_context_t *)ic;
365
366    ctx->stats.actions++;
367    ctx->stats.discards++;
368
369    /* TODO: test returning SIEVE_FAIL */
370    return SIEVE_OK;
371}
372
373static int reject(void *ac, void *ic, void *sc __attribute__((unused)),
374                  void *mc __attribute__((unused)),
375                  const char **errmsg __attribute__((unused)))
376{
377    sieve_reject_context_t *rc = (sieve_reject_context_t *)ac;
378    sieve_test_context_t *ctx = (sieve_test_context_t *)ic;
379
380    ctx->stats.actions++;
381    ctx->stats.rejects++;
382    free(ctx->reject_message);
383    ctx->reject_message = xstrdup(rc->msg);
384
385    /* TODO: test returning SIEVE_FAIL */
386    return SIEVE_OK;
387}
388
389static int fileinto(void *ac, void *ic, void *sc __attribute__((unused)),
390                    void *mc __attribute__((unused)),
391                    const char **errmsg __attribute__((unused)))
392{
393    sieve_fileinto_context_t *fc = (sieve_fileinto_context_t *)ac;
394    sieve_test_context_t *ctx = (sieve_test_context_t *)ic;
395
396    ctx->stats.actions++;
397    ctx->stats.fileintos++;
398    free(ctx->filed_mailbox);
399    ctx->filed_mailbox = xstrdup(fc->mailbox);
400    free(ctx->filed_flags);
401    ctx->filed_flags = strarray_join(fc->imapflags, " ");
402    strarray_free(ctx->flags);
403    ctx->flags = strarray_dup(fc->imapflags);
404
405    /* TODO: test returning SIEVE_FAIL */
406    return SIEVE_OK;
407}
408
409static int keep(void *ac, void *ic, void *sc __attribute__((unused)),
410                void *mc __attribute__((unused)),
411                const char **errmsg __attribute__((unused)))
412{
413    sieve_keep_context_t *kc = (sieve_keep_context_t *)ac;
414    sieve_test_context_t *ctx = (sieve_test_context_t *)ic;
415
416    ctx->stats.actions++;
417    ctx->stats.keeps++;
418    free(ctx->filed_flags);
419    ctx->filed_flags = strarray_join(kc->imapflags, " ");
420    strarray_free(ctx->flags);
421    ctx->flags = strarray_dup(kc->imapflags);
422
423    /* TODO: test returning SIEVE_FAIL */
424    return SIEVE_OK;
425}
426
427static int notify(void *ac, void *ic, void *sc __attribute__((unused)),
428                  void *mc __attribute__((unused)),
429                  const char **errmsg __attribute__((unused)))
430{
431    sieve_notify_context_t *nc = (sieve_notify_context_t *)ac;
432    sieve_test_context_t *ctx = (sieve_test_context_t *)ic;
433    struct buf opts = BUF_INITIALIZER;
434    const char **p;
435
436    for (p = nc->options ; *p ; p++) {
437        if (opts.len)
438            buf_putc(&opts, ' ');
439        buf_appendcstr(&opts, *p);
440    }
441
442    ctx->stats.actions++;
443    ctx->stats.notifies++;
444    free(ctx->notify_options);
445    ctx->notify_options = buf_release(&opts);
446    free(ctx->notify_method);
447    ctx->notify_method = xstrdup(nc->method);
448    free(ctx->notify_priority);
449    ctx->notify_method = xstrdup(nc->priority);
450
451    /* TODO: test returning SIEVE_FAIL */
452    return SIEVE_OK;
453}
454
455static int mysieve_error(int lineno, const char *msg,
456                         void *ic,
457                         void *sc __attribute__((unused)))
458{
459    sieve_test_context_t *ctx = (sieve_test_context_t *)ic;
460    struct buf buf = BUF_INITIALIZER;
461
462    ctx->stats.errors++;
463    buf_printf(&buf, "line %d: %s", lineno, msg);
464    strarray_appendm(ctx->compile_errors, buf_release(&buf));
465
466    return SIEVE_OK;
467}
468
469static int mysieve_execute_error(const char *msg,
470                                 void *ic,
471                                 void *sc __attribute__((unused)),
472                                 void *mc __attribute__((unused)))
473{
474    sieve_test_context_t *ctx = (sieve_test_context_t *)ic;
475
476    ctx->stats.errors++;
477    strarray_append(ctx->run_errors, msg);
478
479    return SIEVE_OK;
480}
481
482static int autorespond(void *ac __attribute__((unused)),
483                       void *ic __attribute__((unused)),
484                       void *sc __attribute__((unused)),
485                       void *mc __attribute__((unused)),
486                       const char **errmsg __attribute__((unused)))
487{
488    return autorespond_reponse;
489}
490
491static int send_response(void *ac, void *ic, void *sc __attribute__((unused)),
492                         void *mc __attribute__((unused)),
493                         const char **errmsg __attribute__((unused)))
494{
495    sieve_send_response_context_t *src = (sieve_send_response_context_t *)ac;
496    sieve_test_context_t *ctx = (sieve_test_context_t *)ic;
497
498    ctx->stats.actions++;
499    ctx->stats.vaction_responses++;
500    free(ctx->vacation_message);
501    ctx->vacation_message = xstrdup(src->msg);
502    free(ctx->vacation_subject);
503    ctx->vacation_subject = xstrdup(src->subj);
504    free(ctx->vacation_to);
505    ctx->vacation_to = xstrdup(src->addr);
506    free(ctx->vacation_from);
507    ctx->vacation_from = xstrdup(src->fromaddr);
508
509    /* TODO: test returning SIEVE_FAIL */
510    return SIEVE_OK;
511}
512
513#ifndef HAVE_FMEMOPEN
514static FILE *fmemopen(const void *buf, size_t len, const char *mode)
515{
516    FILE *fp;
517
518    fp = fopen("/dev/null", mode);
519    if (!fp)
520        return NULL;
521    setbuffer(fp, buf, len);
522    return fp;
523}
524#endif
525
526static void config_read_string(const char *s)
527{
528    char *fname = xstrdup("/tmp/cyrus-cunit-configXXXXXX");
529    int fd = mkstemp(fname);
530    retry_write(fd, s, strlen(s));
531    config_reset();
532    config_read(fname, 0);
533    unlink(fname);
534    free(fname);
535    close(fd);
536}
537
538static int set_up(void)
539{
540    libcyrus_config_setstring(CYRUSOPT_CONFIG_DIR, DBDIR);
541    config_read_string(
542        "configdirectory: "DBDIR"/conf\n"
543        "defaultpartition: "PARTITION"\n"
544        "partition-"PARTITION": "DBDIR"/data\n"
545        "sievenotifier: mailto\n"
546        "sieve_extensions: fileinto reject vacation imapflags notify" \
547	    " envelope body relational regex subaddress copy date index" \
548	    " imap4flags variables\n"
549    );
550    libcyrus_init();
551    return 0;
552}
553
554static int tear_down(void)
555{
556    libcyrus_done();
557    return 0;
558}
559
560static void context_setup(sieve_test_context_t *ctx,
561                          const char *script)
562{
563    int r;
564    static strarray_t mark = STRARRAY_INITIALIZER;
565    static sieve_vacation_t vacation = {
566        0,                      /* min response */
567        0,                      /* max response */
568        &autorespond,           /* autorespond() */
569        &send_response          /* send_response() */
570    };
571    int len = strlen(script);
572    int fd;
573    FILE *fp;
574    sieve_script_t *scr = NULL;
575    bytecode_info_t *bytecode = NULL;
576    char tempfile[32];
577
578    memset(ctx, 0, sizeof(*ctx));
579    if (!mark.count)
580        strarray_append(&mark, "\\flagged");
581
582    ctx->compile_errors = strarray_new();
583    ctx->run_errors = strarray_new();
584
585    ctx->interp = sieve_interp_alloc(ctx);
586    sieve_register_redirect(ctx->interp, redirect);
587    sieve_register_discard(ctx->interp, discard);
588    sieve_register_reject(ctx->interp, reject);
589    sieve_register_fileinto(ctx->interp, fileinto);
590    sieve_register_keep(ctx->interp, keep);
591    sieve_register_size(ctx->interp, getsize);
592    sieve_register_header(ctx->interp, getheader);
593    sieve_register_envelope(ctx->interp, getheader);
594    sieve_register_body(ctx->interp, getbody);
595    sieve_register_include(ctx->interp, getinclude);
596    sieve_register_vacation(ctx->interp, &vacation);
597    sieve_register_imapflags(ctx->interp, &mark);
598    sieve_register_notify(ctx->interp, notify);
599    sieve_register_parse_error(ctx->interp, mysieve_error);
600    sieve_register_execute_error(ctx->interp, mysieve_execute_error);
601
602    /* Here we pretend to be the sieve compiler, and generate
603     * a file of compiled bytecode from the script string */
604    fp = fmemopen((void *)script, len, "r");
605    r = sieve_script_parse(ctx->interp, fp, ctx, &scr);
606    CU_ASSERT_EQUAL(r, SIEVE_OK);
607    fclose(fp);
608
609    r = sieve_generate_bytecode(&bytecode, scr);
610    CU_ASSERT(r > 0);
611    strcpy(tempfile, "/tmp/sievetest-BC-XXXXXX");
612    fd = mkstemp(tempfile);
613    CU_ASSERT(fd >= 0);
614    r = sieve_emit_bytecode(fd, bytecode);
615    CU_ASSERT(r > 0);
616    sieve_free_bytecode(&bytecode);
617    sieve_script_free(&scr);
618
619    /* Now load the compiled bytecode */
620    r = sieve_script_load(tempfile, &ctx->exe);
621    CU_ASSERT_EQUAL(r, SIEVE_OK);
622    unlink(tempfile);
623}
624
625static void context_cleanup(sieve_test_context_t *ctx)
626{
627    int r;
628
629    if (verbose > 1) {
630        int i;
631
632        fprintf(stderr, "sieve test context\n");
633        fprintf(stderr, "    actions: %u\n", ctx->stats.actions);
634        fprintf(stderr, "    errors: %u\n", ctx->stats.errors);
635        fprintf(stderr, "    redirects: %u\n", ctx->stats.redirects);
636        fprintf(stderr, "    discards: %u\n", ctx->stats.discards);
637        fprintf(stderr, "    rejects: %u\n", ctx->stats.rejects);
638        fprintf(stderr, "    fileintos: %u\n", ctx->stats.fileintos);
639        fprintf(stderr, "    keeps: %u\n", ctx->stats.keeps);
640        fprintf(stderr, "    notifies: %u\n", ctx->stats.notifies);
641        fprintf(stderr, "    vaction_responses: %u\n", ctx->stats.vaction_responses);
642        if (ctx->redirected_to)
643            fprintf(stderr, "    redirected_to: %s\n", ctx->redirected_to);
644        if (ctx->reject_message)
645            fprintf(stderr, "    reject_message: %s\n", ctx->reject_message);
646        if (ctx->filed_mailbox)
647            fprintf(stderr, "    filed_mailbox: %s\n", ctx->filed_mailbox);
648        if (ctx->filed_flags)
649            fprintf(stderr, "    filed_flags: %s\n", ctx->filed_flags);
650        if (ctx->notify_method)
651            fprintf(stderr, "    notify_method: %s\n", ctx->notify_method);
652        if (ctx->notify_priority)
653            fprintf(stderr, "    notify_priority: %s\n", ctx->notify_priority);
654        if (ctx->notify_options)
655            fprintf(stderr, "    notify_options: %s\n", ctx->notify_options);
656        if (ctx->vacation_message)
657            fprintf(stderr, "    vacation_message: %s\n", ctx->vacation_message);
658        if (ctx->vacation_subject)
659            fprintf(stderr, "    vacation_subject: %s\n", ctx->vacation_subject);
660        if (ctx->vacation_to)
661            fprintf(stderr, "    vacation_to: %s\n", ctx->vacation_to);
662        if (ctx->vacation_from)
663            fprintf(stderr, "    vacation_from: %s\n", ctx->vacation_from);
664        if (ctx->compile_errors->count) {
665            fprintf(stderr, "    compile_errors:\n");
666            for (i = 0 ; i < ctx->compile_errors->count ; i++)
667                fprintf(stderr, "\t[%d] %s\n", i, ctx->compile_errors->data[i]);
668        }
669        if (ctx->run_errors->count) {
670            fprintf(stderr, "    run_errors:\n");
671            for (i = 0 ; i < ctx->run_errors->count ; i++)
672                fprintf(stderr, "\t[%d] %s\n", i, ctx->run_errors->data[i]);
673        }
674    }
675
676    /*used to be sieve_script_free*/
677    r = sieve_script_unload(&ctx->exe);
678    CU_ASSERT_EQUAL(r, SIEVE_OK);
679    CU_ASSERT_PTR_NULL(ctx->exe);
680
681    r = sieve_interp_free(&ctx->interp);
682    CU_ASSERT_EQUAL(r, SIEVE_OK);
683
684    free(ctx->redirected_to);
685    free(ctx->reject_message);
686    free(ctx->filed_mailbox);
687    free(ctx->filed_flags);
688    free(ctx->notify_method);
689    free(ctx->notify_priority);
690    free(ctx->notify_options);
691    free(ctx->vacation_message);
692    free(ctx->vacation_subject);
693    free(ctx->vacation_to);
694    free(ctx->vacation_from);
695    strarray_free(ctx->compile_errors);
696    strarray_free(ctx->run_errors);
697    strarray_free(ctx->flags);
698}
699
700
701static sieve_test_message_t *sieve_test_message_new(const char *text, int len)
702{
703    sieve_test_message_t *msg;
704    struct protstream *pin;
705    FILE *fout;
706    int fd;
707    int r;
708    char tempfile[32];
709
710    msg = xzmalloc(sizeof(sieve_test_message_t));
711    msg->text = text;
712    msg->length = len;
713    msg->headers = spool_new_hdrcache();
714
715    strcpy(tempfile, "/tmp/sievetest-MS-XXXXXX");
716    fd = mkstemp(tempfile);
717    CU_ASSERT(fd >= 0);
718    msg->filename = xstrdup(tempfile);
719    r = retry_write(fd, text, len);
720    CU_ASSERT_EQUAL(r, len);
721    lseek(fd, SEEK_SET, 0);
722
723    pin = prot_new(fd, /*read*/0);
724    CU_ASSERT_PTR_NOT_NULL(pin);
725
726    fout = fopen("/dev/null", "w");
727    CU_ASSERT_PTR_NOT_NULL(fout);
728
729    r = spool_fill_hdrcache(pin, fout, msg->headers, NULL);
730    CU_ASSERT_EQUAL(r, 0);
731
732    fclose(fout);
733    prot_free(pin);
734
735    return msg;
736}
737
738static void message_free(sieve_test_message_t *msg)
739{
740    spool_free_hdrcache(msg->headers);
741    if (msg->content.body)
742        message_free_body(msg->content.body);
743    if (msg->content.base)
744        map_free(&msg->content.base, &msg->content.len);
745    unlink(msg->filename);
746    free(msg->filename);
747    free(msg);
748}
749
750
751static void run_message(sieve_test_context_t *ctx,
752                        const char *text)
753{
754    sieve_test_message_t *msg;
755    int r;
756
757    msg = sieve_test_message_new(text, strlen(text));
758    CU_ASSERT_PTR_NOT_NULL(msg);
759
760    r = sieve_execute_bytecode(ctx->exe, ctx->interp, ctx, msg);
761    CU_ASSERT_EQUAL(r, SIEVE_OK);
762
763    message_free(msg);
764}
765
766static void test_address_all(void)
767{
768    static const char SCRIPT_IS[] =
769    "if address :all :is \"from\" \"zme@true.com\"\n"
770    "{redirect \"me@blah.com\";}\n"
771    ;
772    static const char SCRIPT_CONTAINS[] =
773    "if address :all :contains \"from\" \"true.com\"\n"
774    "{redirect \"me@blah.com\";}\n"
775    ;
776    static const char SCRIPT_MATCHES[] =
777    "if address :all :matches \"from\" \"*true.com\"\n"
778    "{redirect \"me@blah.com\";}\n"
779    ;
780
781    static const char MSG_TRUE[] =
782    "Date: Mon, 25 Jan 2003 08:51:06 -0500\r\n"
783    "From: zme@true.com\r\n"
784    "To: you\r\n"
785    "Subject: simple address test\r\n"
786    "\r\n"
787    "blah\n"
788    ;
789    static const char MSG_FALSE[] =
790    "Date: Mon, 25 Jan 2003 08:51:06 -0500\r\n"
791    "From: zme@false.com\r\n"
792    "To: you\r\n"
793    "Subject: simple address test\r\n"
794    "\r\n"
795    "blah\n"
796    ;
797    sieve_test_context_t ctx;
798
799    /* Test :is */
800    context_setup(&ctx, SCRIPT_IS);
801    CU_ASSERT_EQUAL(ctx.stats.errors, 0);
802
803    run_message(&ctx, MSG_TRUE);
804    CU_ASSERT_EQUAL(ctx.stats.errors, 0);
805    CU_ASSERT_EQUAL(ctx.stats.actions, 1);
806    CU_ASSERT_EQUAL(ctx.stats.redirects, 1);
807    CU_ASSERT_EQUAL(ctx.stats.keeps, 0);
808    CU_ASSERT_STRING_EQUAL(ctx.redirected_to, "me@blah.com");
809
810    run_message(&ctx, MSG_FALSE);
811    CU_ASSERT_EQUAL(ctx.stats.errors, 0);
812    CU_ASSERT_EQUAL(ctx.stats.actions, 2);
813    CU_ASSERT_EQUAL(ctx.stats.redirects, 1);
814    CU_ASSERT_EQUAL(ctx.stats.keeps, 1);
815    CU_ASSERT_STRING_EQUAL(ctx.redirected_to, "me@blah.com");
816
817    context_cleanup(&ctx);
818
819    /* Test :contains */
820    context_setup(&ctx, SCRIPT_CONTAINS);
821    CU_ASSERT_EQUAL(ctx.stats.errors, 0);
822
823    run_message(&ctx, MSG_TRUE);
824    CU_ASSERT_EQUAL(ctx.stats.errors, 0);
825    CU_ASSERT_EQUAL(ctx.stats.actions, 1);
826    CU_ASSERT_EQUAL(ctx.stats.redirects, 1);
827    CU_ASSERT_EQUAL(ctx.stats.keeps, 0);
828    CU_ASSERT_STRING_EQUAL(ctx.redirected_to, "me@blah.com");
829
830    run_message(&ctx, MSG_FALSE);
831    CU_ASSERT_EQUAL(ctx.stats.errors, 0);
832    CU_ASSERT_EQUAL(ctx.stats.actions, 2);
833    CU_ASSERT_EQUAL(ctx.stats.redirects, 1);
834    CU_ASSERT_EQUAL(ctx.stats.keeps, 1);
835    CU_ASSERT_STRING_EQUAL(ctx.redirected_to, "me@blah.com");
836
837    context_cleanup(&ctx);
838
839    /* Test :matches */
840    context_setup(&ctx, SCRIPT_MATCHES);
841    CU_ASSERT_EQUAL(ctx.stats.errors, 0);
842
843    run_message(&ctx, MSG_TRUE);
844    CU_ASSERT_EQUAL(ctx.stats.errors, 0);
845    CU_ASSERT_EQUAL(ctx.stats.actions, 1);
846    CU_ASSERT_EQUAL(ctx.stats.redirects, 1);
847    CU_ASSERT_EQUAL(ctx.stats.keeps, 0);
848    CU_ASSERT_STRING_EQUAL(ctx.redirected_to, "me@blah.com");
849
850    run_message(&ctx, MSG_FALSE);
851    CU_ASSERT_EQUAL(ctx.stats.errors, 0);
852    CU_ASSERT_EQUAL(ctx.stats.actions, 2);
853    CU_ASSERT_EQUAL(ctx.stats.redirects, 1);
854    CU_ASSERT_EQUAL(ctx.stats.keeps, 1);
855    CU_ASSERT_STRING_EQUAL(ctx.redirected_to, "me@blah.com");
856
857    context_cleanup(&ctx);
858}
859
860static void test_exists(void)
861{
862    static const char SCRIPT[] =
863    "if exists \"flooglewart\"\n"
864    "{redirect \"me@blah.com\";}\n"
865    ;
866    static const char MSG_TRUE[] =
867    "Date: Mon, 25 Jan 2003 08:51:06 -0500\r\n"
868    "From: zme@true.com\r\n"
869    "To: you\r\n"
870    "Subject: simple address test\r\n"
871    "Flooglewart: fnarp fmeh oogedyboogedy\r\n"
872    "\r\n"
873    "blah\n"
874    ;
875    static const char MSG_FALSE[] =
876    "Date: Mon, 25 Jan 2003 08:51:06 -0500\r\n"
877    "From: yme@false.com\r\n"
878    "To: you\r\n"
879    "Subject: simple address test\r\n"
880    "\r\n"
881    "blah\n"
882    ;
883    sieve_test_context_t ctx;
884
885    context_setup(&ctx, SCRIPT);
886    CU_ASSERT_EQUAL(ctx.stats.errors, 0);
887
888    run_message(&ctx, MSG_TRUE);
889    CU_ASSERT_EQUAL(ctx.stats.errors, 0);
890    CU_ASSERT_EQUAL(ctx.stats.actions, 1);
891    CU_ASSERT_EQUAL(ctx.stats.redirects, 1);
892    CU_ASSERT_EQUAL(ctx.stats.keeps, 0);
893    CU_ASSERT_STRING_EQUAL(ctx.redirected_to, "me@blah.com");
894
895    run_message(&ctx, MSG_FALSE);
896    CU_ASSERT_EQUAL(ctx.stats.errors, 0);
897    CU_ASSERT_EQUAL(ctx.stats.actions, 2);
898    CU_ASSERT_EQUAL(ctx.stats.redirects, 1);
899    CU_ASSERT_EQUAL(ctx.stats.keeps, 1);
900    CU_ASSERT_STRING_EQUAL(ctx.redirected_to, "me@blah.com");
901
902    context_cleanup(&ctx);
903}
904
905static void test_address_domain(void)
906{
907    static const char SCRIPT[] =
908    "if address :domain :is \"from\" \"true.com\"\n"
909    "{redirect \"me@blah.com\";}\n"
910    ;
911    static const char MSG_TRUE[] =
912    "Date: Mon, 25 Jan 2003 08:51:06 -0500\r\n"
913    "From: zme@true.com\r\n"
914    "To: you\r\n"
915    "Subject: simple address test\r\n"
916    "\r\n"
917    "blah\n"
918    ;
919    static const char MSG_FALSE[] =
920    "Date: Mon, 25 Jan 2003 08:51:06 -0500\r\n"
921    "From: zme@false.com\r\n"
922    "To: you\r\n"
923    "Subject: simple address test\r\n"
924    "\r\n"
925    "blah\n"
926    ;
927    sieve_test_context_t ctx;
928
929    context_setup(&ctx, SCRIPT);
930    CU_ASSERT_EQUAL(ctx.stats.errors, 0);
931
932    run_message(&ctx, MSG_TRUE);
933    CU_ASSERT_EQUAL(ctx.stats.errors, 0);
934    CU_ASSERT_EQUAL(ctx.stats.actions, 1);
935    CU_ASSERT_EQUAL(ctx.stats.redirects, 1);
936    CU_ASSERT_EQUAL(ctx.stats.keeps, 0);
937    CU_ASSERT_STRING_EQUAL(ctx.redirected_to, "me@blah.com");
938
939    run_message(&ctx, MSG_FALSE);
940    CU_ASSERT_EQUAL(ctx.stats.errors, 0);
941    CU_ASSERT_EQUAL(ctx.stats.actions, 2);
942    CU_ASSERT_EQUAL(ctx.stats.redirects, 1);
943    CU_ASSERT_EQUAL(ctx.stats.keeps, 1);
944    CU_ASSERT_STRING_EQUAL(ctx.redirected_to, "me@blah.com");
945
946    context_cleanup(&ctx);
947}
948
949static void test_address_localpart(void)
950{
951    static const char SCRIPT[] =
952    "if address :localpart :is \"from\" \"zme\"\n"
953    "{redirect \"me@blah.com\";}\n"
954    ;
955    static const char MSG_TRUE[] =
956    "Date: Mon, 25 Jan 2003 08:51:06 -0500\r\n"
957    "From: zme@true.com\r\n"
958    "To: you\r\n"
959    "Subject: simple address test\r\n"
960    "\r\n"
961    "blah\n"
962    ;
963    static const char MSG_FALSE[] =
964    "Date: Mon, 25 Jan 2003 08:51:06 -0500\r\n"
965    "From: yme@false.com\r\n"
966    "To: you\r\n"
967    "Subject: simple address test\r\n"
968    "\r\n"
969    "blah\n"
970    ;
971    sieve_test_context_t ctx;
972
973    context_setup(&ctx, SCRIPT);
974    CU_ASSERT_EQUAL(ctx.stats.errors, 0);
975
976    run_message(&ctx, MSG_TRUE);
977    CU_ASSERT_EQUAL(ctx.stats.errors, 0);
978    CU_ASSERT_EQUAL(ctx.stats.actions, 1);
979    CU_ASSERT_EQUAL(ctx.stats.redirects, 1);
980    CU_ASSERT_EQUAL(ctx.stats.keeps, 0);
981    CU_ASSERT_STRING_EQUAL(ctx.redirected_to, "me@blah.com");
982
983    run_message(&ctx, MSG_FALSE);
984    CU_ASSERT_EQUAL(ctx.stats.errors, 0);
985    CU_ASSERT_EQUAL(ctx.stats.actions, 2);
986    CU_ASSERT_EQUAL(ctx.stats.redirects, 1);
987    CU_ASSERT_EQUAL(ctx.stats.keeps, 1);
988    CU_ASSERT_STRING_EQUAL(ctx.redirected_to, "me@blah.com");
989
990    context_cleanup(&ctx);
991}
992
993static void test_address_index(void)
994{
995    static const char SCRIPT[] =
996    "require [\"index\"];\n"
997    "if address :index 2 :is \"from\" \"zme@true.com\"\n"
998    "{redirect \"me@blah.com\";}\n"
999    ;
1000
1001    static const char SCRIPT_LAST[] =
1002    "require [\"index\"];\n"
1003    "if address :index 3 :last :is \"from\" \"zme@true.com\"\n"
1004    "{redirect \"me@blah.com\";}\n"
1005    ;
1006
1007    static const char MSG_TRUE[] =
1008    "Date: Mon, 25 Jan 2003 08:51:06 -0500\r\n"
1009    "From: zme@false.com\r\n"
1010    "From: zme@true.com\r\n"
1011    "From: zme@false.com\r\n"
1012    "From: zme@false.com\r\n"
1013    "To: you\r\n"
1014    "Subject: simple address test\r\n"
1015    "\r\n"
1016    "blah\n"
1017    ;
1018    static const char MSG_FALSE[] =
1019    "Date: Mon, 25 Jan 2003 08:51:06 -0500\r\n"
1020    "From: zme@true.com\r\n"
1021    "From: zme@false.com\r\n"
1022    "From: zme@true.com\r\n"
1023    "From: zme@true.com\r\n"
1024    "To: you\r\n"
1025    "Subject: simple address test\r\n"
1026    "\r\n"
1027    "blah\n"
1028    ;
1029    sieve_test_context_t ctx;
1030
1031    /* Test :index */
1032    context_setup(&ctx, SCRIPT);
1033    CU_ASSERT_EQUAL(ctx.stats.errors, 0);
1034
1035    run_message(&ctx, MSG_TRUE);
1036    CU_ASSERT_EQUAL(ctx.stats.errors, 0);
1037    CU_ASSERT_EQUAL(ctx.stats.actions, 1);
1038    CU_ASSERT_EQUAL(ctx.stats.redirects, 1);
1039    CU_ASSERT_EQUAL(ctx.stats.keeps, 0);
1040    CU_ASSERT_STRING_EQUAL(ctx.redirected_to, "me@blah.com");
1041
1042    run_message(&ctx, MSG_FALSE);
1043    CU_ASSERT_EQUAL(ctx.stats.errors, 0);
1044    CU_ASSERT_EQUAL(ctx.stats.actions, 2);
1045    CU_ASSERT_EQUAL(ctx.stats.redirects, 1);
1046    CU_ASSERT_EQUAL(ctx.stats.keeps, 1);
1047    CU_ASSERT_STRING_EQUAL(ctx.redirected_to, "me@blah.com");
1048
1049    context_cleanup(&ctx);
1050
1051    /* Test :index :last */
1052    context_setup(&ctx, SCRIPT_LAST);
1053    CU_ASSERT_EQUAL(ctx.stats.errors, 0);
1054
1055    run_message(&ctx, MSG_TRUE);
1056    CU_ASSERT_EQUAL(ctx.stats.errors, 0);
1057    CU_ASSERT_EQUAL(ctx.stats.actions, 1);
1058    CU_ASSERT_EQUAL(ctx.stats.redirects, 1);
1059    CU_ASSERT_EQUAL(ctx.stats.keeps, 0);
1060    CU_ASSERT_STRING_EQUAL(ctx.redirected_to, "me@blah.com");
1061
1062    run_message(&ctx, MSG_FALSE);
1063    CU_ASSERT_EQUAL(ctx.stats.errors, 0);
1064    CU_ASSERT_EQUAL(ctx.stats.actions, 2);
1065    CU_ASSERT_EQUAL(ctx.stats.redirects, 1);
1066    CU_ASSERT_EQUAL(ctx.stats.keeps, 1);
1067    CU_ASSERT_STRING_EQUAL(ctx.redirected_to, "me@blah.com");
1068
1069    context_cleanup(&ctx);
1070}
1071
1072static void test_header_index(void)
1073{
1074    static const char SCRIPT[] =
1075    "require [\"index\"];\n"
1076    "if header :index 3 :is \"X-Virus-Status\" \"Clean\"\n"
1077    "{redirect \"me@blah.com\";}\n"
1078    ;
1079
1080    static const char SCRIPT_LAST[] =
1081    "require [\"index\"];\n"
1082    "if header :index 2 :last :is \"X-Virus-Status\" \"Clean\"\n"
1083    "{redirect \"me@blah.com\";}\n"
1084    ;
1085
1086    static const char MSG_TRUE[] =
1087    "Date: Mon, 25 Jan 2003 08:51:06 -0500\r\n"
1088    "From: zme@true.com\r\n"
1089    "To: you\r\n"
1090    "Subject: simple address test\r\n"
1091    "X-Virus-Status: *****\r\n"
1092    "X-Virus-Status: *****\r\n"
1093    "X-Virus-Status: Clean\r\n"
1094    "X-Virus-Status: *****\r\n"
1095    "\r\n"
1096    "blah\n"
1097    ;
1098    static const char MSG_FALSE[] =
1099    "Date: Mon, 25 Jan 2003 08:51:06 -0500\r\n"
1100    "From: zme@false.com\r\n"
1101    "To: you\r\n"
1102    "Subject: simple address test\r\n"
1103    "X-Virus-Status: Clean\r\n"
1104    "X-Virus-Status: Clean\r\n"
1105    "X-Virus-Status: *****\r\n"
1106    "X-Virus-Status: Clean\r\n"
1107    "\r\n"
1108    "blah\n"
1109    ;
1110    sieve_test_context_t ctx;
1111
1112    /* Test :index */
1113    context_setup(&ctx, SCRIPT);
1114    CU_ASSERT_EQUAL(ctx.stats.errors, 0);
1115
1116    run_message(&ctx, MSG_TRUE);
1117    CU_ASSERT_EQUAL(ctx.stats.errors, 0);
1118    CU_ASSERT_EQUAL(ctx.stats.actions, 1);
1119    CU_ASSERT_EQUAL(ctx.stats.redirects, 1);
1120    CU_ASSERT_EQUAL(ctx.stats.keeps, 0);
1121    CU_ASSERT_STRING_EQUAL(ctx.redirected_to, "me@blah.com");
1122
1123    run_message(&ctx, MSG_FALSE);
1124    CU_ASSERT_EQUAL(ctx.stats.errors, 0);
1125    CU_ASSERT_EQUAL(ctx.stats.actions, 2);
1126    CU_ASSERT_EQUAL(ctx.stats.redirects, 1);
1127    CU_ASSERT_EQUAL(ctx.stats.keeps, 1);
1128    CU_ASSERT_STRING_EQUAL(ctx.redirected_to, "me@blah.com");
1129
1130    context_cleanup(&ctx);
1131
1132    /* Test :index :last */
1133    context_setup(&ctx, SCRIPT_LAST);
1134    CU_ASSERT_EQUAL(ctx.stats.errors, 0);
1135
1136    run_message(&ctx, MSG_TRUE);
1137    CU_ASSERT_EQUAL(ctx.stats.errors, 0);
1138    CU_ASSERT_EQUAL(ctx.stats.actions, 1);
1139    CU_ASSERT_EQUAL(ctx.stats.redirects, 1);
1140    CU_ASSERT_EQUAL(ctx.stats.keeps, 0);
1141    CU_ASSERT_STRING_EQUAL(ctx.redirected_to, "me@blah.com");
1142
1143    run_message(&ctx, MSG_FALSE);
1144    CU_ASSERT_EQUAL(ctx.stats.errors, 0);
1145    CU_ASSERT_EQUAL(ctx.stats.actions, 2);
1146    CU_ASSERT_EQUAL(ctx.stats.redirects, 1);
1147    CU_ASSERT_EQUAL(ctx.stats.keeps, 1);
1148    CU_ASSERT_STRING_EQUAL(ctx.redirected_to, "me@blah.com");
1149
1150    context_cleanup(&ctx);
1151}
1152
1153static void test_date_year(void)
1154{
1155    static const char SCRIPT[] =
1156    "require [\"date\", \"index\"];\n"
1157    "if date :index 1 :last :is \"received\" \"year\" [ \"1983\", \"1993\", \"2003\", \"2013\" ]\n"
1158    "{redirect \"me@blah.com\";}\n"
1159    ;
1160    static const char MSG_TRUE[] =
1161    "Date: Sat, 16 Nov 2013 12:46:49 +1100\r\n"
1162    "Received: from localhost (localhost [127.0.0.1])\r\n"
1163    "           by mail.com (Cyrus v2.3.16) with LMTPA;\r\n"
1164    "           Tue, 16 Nov 2010 12:50:12 +1100\r\n"
1165    "Received: from localhost (localhost [127.0.0.1])\r\n"
1166    "           by mail.com (Cyrus v2.3.16) with LMTPA;\r\n"
1167    "           Tue, 16 Nov 2013 12:50:12 +1100\r\n"
1168    "From: zme@true.com\r\n"
1169    "To: you\r\n"
1170    "Subject: simple address test\r\n"
1171    "\r\n"
1172    "blah\n"
1173    ;
1174    static const char MSG_FALSE[] =
1175    "Date: Tue, 16 Nov 2010 12:46:49 +1100\r\n"
1176    "Received: from localhost (localhost [127.0.0.1])\r\n"
1177    "           by mail.com (Cyrus v2.3.16) with LMTPA;\r\n"
1178    "           Tue, 16 Nov 2013 12:50:12 +1100\r\n"
1179    "Received: from localhost (localhost [127.0.0.1])\r\n"
1180    "           by mail.com (Cyrus v2.3.16) with LMTPA;\r\n"
1181    "           Tue, 16 Nov 2010 12:50:12 +1100\r\n"
1182    "From: yme@false.com\r\n"
1183    "To: you\r\n"
1184    "Subject: simple address test\r\n"
1185    "\r\n"
1186    "blah\n"
1187    ;
1188    sieve_test_context_t ctx;
1189
1190    context_setup(&ctx, SCRIPT);
1191    CU_ASSERT_EQUAL(ctx.stats.errors, 0);
1192
1193    run_message(&ctx, MSG_TRUE);
1194    CU_ASSERT_EQUAL(ctx.stats.errors, 0);
1195    CU_ASSERT_EQUAL(ctx.stats.actions, 1);
1196    CU_ASSERT_EQUAL(ctx.stats.redirects, 1);
1197    CU_ASSERT_EQUAL(ctx.stats.keeps, 0);
1198    CU_ASSERT_STRING_EQUAL(ctx.redirected_to, "me@blah.com");
1199
1200    run_message(&ctx, MSG_FALSE);
1201    CU_ASSERT_EQUAL(ctx.stats.errors, 0);
1202    CU_ASSERT_EQUAL(ctx.stats.actions, 2);
1203    CU_ASSERT_EQUAL(ctx.stats.redirects, 1);
1204    CU_ASSERT_EQUAL(ctx.stats.keeps, 1);
1205    CU_ASSERT_STRING_EQUAL(ctx.redirected_to, "me@blah.com");
1206
1207    context_cleanup(&ctx);
1208}
1209
1210static void test_date_zone_month(void)
1211{
1212    static const char SCRIPT[] =
1213    "require [\"date\"];\n"
1214    "if date :is :zone \"-0800\" \"date\" \"month\" \"11\"\n"
1215    "{redirect \"me@blah.com\";}\n"
1216    ;
1217    static const char MSG_TRUE[] =
1218    "Date: Fri, 1 Nov 2013 19:46:49 +1100\r\n"
1219    "From: zme@true.com\r\n"
1220    "To: you\r\n"
1221    "Subject: simple address test\r\n"
1222    "\r\n"
1223    "blah\n"
1224    ;
1225    static const char MSG_FALSE[] =
1226    "Date: Fri, 1 Nov 2013 11:46:49 +1100\r\n"
1227    "From: yme@false.com\r\n"
1228    "To: you\r\n"
1229    "Subject: simple address test\r\n"
1230    "\r\n"
1231    "blah\n"
1232    ;
1233    sieve_test_context_t ctx;
1234
1235    context_setup(&ctx, SCRIPT);
1236    CU_ASSERT_EQUAL(ctx.stats.errors, 0);
1237
1238    run_message(&ctx, MSG_TRUE);
1239    CU_ASSERT_EQUAL(ctx.stats.errors, 0);
1240    CU_ASSERT_EQUAL(ctx.stats.actions, 1);
1241    CU_ASSERT_EQUAL(ctx.stats.redirects, 1);
1242    CU_ASSERT_EQUAL(ctx.stats.keeps, 0);
1243    CU_ASSERT_STRING_EQUAL(ctx.redirected_to, "me@blah.com");
1244
1245    run_message(&ctx, MSG_FALSE);
1246    CU_ASSERT_EQUAL(ctx.stats.errors, 0);
1247    CU_ASSERT_EQUAL(ctx.stats.actions, 2);
1248    CU_ASSERT_EQUAL(ctx.stats.redirects, 1);
1249    CU_ASSERT_EQUAL(ctx.stats.keeps, 1);
1250    CU_ASSERT_STRING_EQUAL(ctx.redirected_to, "me@blah.com");
1251
1252    context_cleanup(&ctx);
1253}
1254
1255static void test_date_date(void)
1256{
1257    static const char SCRIPT[] =
1258    "require [\"date\"];\n"
1259    "if date :is :originalzone \"date\" \"date\" \"2013-11-02\"\n"
1260    "{redirect \"me@blah.com\";}\n"
1261    ;
1262    static const char MSG_TRUE[] =
1263    "Date: Sat, 2 Nov 2013 19:46:49 +1100\r\n"
1264    "From: zme@true.com\r\n"
1265    "To: you\r\n"
1266    "Subject: simple address test\r\n"
1267    "\r\n"
1268    "blah\n"
1269    ;
1270    static const char MSG_FALSE[] =
1271    "Date: Fri, 1 Nov 2013 19:45:49 +1100\r\n"
1272    "From: yme@false.com\r\n"
1273    "To: you\r\n"
1274    "Subject: simple address test\r\n"
1275    "\r\n"
1276    "blah\n"
1277    ;
1278    sieve_test_context_t ctx;
1279
1280    context_setup(&ctx, SCRIPT);
1281    CU_ASSERT_EQUAL(ctx.stats.errors, 0);
1282
1283    run_message(&ctx, MSG_TRUE);
1284    CU_ASSERT_EQUAL(ctx.stats.errors, 0);
1285    CU_ASSERT_EQUAL(ctx.stats.actions, 1);
1286    CU_ASSERT_EQUAL(ctx.stats.redirects, 1);
1287    CU_ASSERT_EQUAL(ctx.stats.keeps, 0);
1288    CU_ASSERT_STRING_EQUAL(ctx.redirected_to, "me@blah.com");
1289
1290    run_message(&ctx, MSG_FALSE);
1291    CU_ASSERT_EQUAL(ctx.stats.errors, 0);
1292    CU_ASSERT_EQUAL(ctx.stats.actions, 2);
1293    CU_ASSERT_EQUAL(ctx.stats.redirects, 1);
1294    CU_ASSERT_EQUAL(ctx.stats.keeps, 1);
1295    CU_ASSERT_STRING_EQUAL(ctx.redirected_to, "me@blah.com");
1296
1297    context_cleanup(&ctx);
1298}
1299
1300static void test_date_time(void)
1301{
1302    static const char SCRIPT[] =
1303    "require [\"date\"];\n"
1304    "if date :is :originalzone \"date\" \"time\" \"19:46:49\"\n"
1305    "{redirect \"me@blah.com\";}\n"
1306    ;
1307    static const char MSG_TRUE[] =
1308    "Date: Sat, 2 Nov 2013 19:46:49 +1100\r\n"
1309    "From: zme@true.com\r\n"
1310    "To: you\r\n"
1311    "Subject: simple address test\r\n"
1312    "\r\n"
1313    "blah\n"
1314    ;
1315    static const char MSG_FALSE[] =
1316    "Date: Sat, 2 Nov 2013 19:45:49 +1100\r\n"
1317    "From: yme@false.com\r\n"
1318    "To: you\r\n"
1319    "Subject: simple address test\r\n"
1320    "\r\n"
1321    "blah\n"
1322    ;
1323    sieve_test_context_t ctx;
1324
1325    context_setup(&ctx, SCRIPT);
1326    CU_ASSERT_EQUAL(ctx.stats.errors, 0);
1327
1328    run_message(&ctx, MSG_TRUE);
1329    CU_ASSERT_EQUAL(ctx.stats.errors, 0);
1330    CU_ASSERT_EQUAL(ctx.stats.actions, 1);
1331    CU_ASSERT_EQUAL(ctx.stats.redirects, 1);
1332    CU_ASSERT_EQUAL(ctx.stats.keeps, 0);
1333    CU_ASSERT_STRING_EQUAL(ctx.redirected_to, "me@blah.com");
1334
1335    run_message(&ctx, MSG_FALSE);
1336    CU_ASSERT_EQUAL(ctx.stats.errors, 0);
1337    CU_ASSERT_EQUAL(ctx.stats.actions, 2);
1338    CU_ASSERT_EQUAL(ctx.stats.redirects, 1);
1339    CU_ASSERT_EQUAL(ctx.stats.keeps, 1);
1340    CU_ASSERT_STRING_EQUAL(ctx.redirected_to, "me@blah.com");
1341
1342    context_cleanup(&ctx);
1343}
1344
1345static void test_date_originalzone_day(void)
1346{
1347    static const char SCRIPT[] =
1348    "require [\"date\"];\n"
1349    "if date :is :originalzone \"date\" \"day\" \"16\"\n"
1350    "{redirect \"me@blah.com\";}\n"
1351    ;
1352    static const char MSG_TRUE[] =
1353    "Date: Sat, 16 Nov 2013 12:46:49 +1100\r\n"
1354    "From: zme@true.com\r\n"
1355    "To: you\r\n"
1356    "Subject: simple address test\r\n"
1357    "\r\n"
1358    "blah\n"
1359    ;
1360    static const char MSG_FALSE[] =
1361    "Date: Fri, 15 Nov 2013 12:46:49 +1100\r\n"
1362    "From: yme@false.com\r\n"
1363    "To: you\r\n"
1364    "Subject: simple address test\r\n"
1365    "\r\n"
1366    "blah\n"
1367    ;
1368    sieve_test_context_t ctx;
1369
1370    context_setup(&ctx, SCRIPT);
1371    CU_ASSERT_EQUAL(ctx.stats.errors, 0);
1372
1373    run_message(&ctx, MSG_TRUE);
1374    CU_ASSERT_EQUAL(ctx.stats.errors, 0);
1375    CU_ASSERT_EQUAL(ctx.stats.actions, 1);
1376    CU_ASSERT_EQUAL(ctx.stats.redirects, 1);
1377    CU_ASSERT_EQUAL(ctx.stats.keeps, 0);
1378    CU_ASSERT_STRING_EQUAL(ctx.redirected_to, "me@blah.com");
1379
1380    run_message(&ctx, MSG_FALSE);
1381    CU_ASSERT_EQUAL(ctx.stats.errors, 0);
1382    CU_ASSERT_EQUAL(ctx.stats.actions, 2);
1383    CU_ASSERT_EQUAL(ctx.stats.redirects, 1);
1384    CU_ASSERT_EQUAL(ctx.stats.keeps, 1);
1385    CU_ASSERT_STRING_EQUAL(ctx.redirected_to, "me@blah.com");
1386
1387    context_cleanup(&ctx);
1388}
1389
1390static void test_date_weekend_weekday(void)
1391{
1392    static const char SCRIPT[] =
1393    "require [\"date\"];\n"
1394    "if anyof(date :is :zone \"-0800\" \"date\" \"weekday\" \"0\",\n"
1395    "         date :is :zone \"-0800\" \"date\" \"weekday\" \"6\")\n"
1396    "{redirect \"me@blah.com\";}\n"
1397    ;
1398    static const char MSG_TRUE[] =
1399    "Date: Sat, 2 Nov 2013 19:46:49 +1100\r\n"
1400    "From: zme@true.com\r\n"
1401    "To: you\r\n"
1402    "Subject: simple address test\r\n"
1403    "\r\n"
1404    "blah\n"
1405    ;
1406    static const char MSG_FALSE[] =
1407    "Date: Fri, 1 Nov 2013 11:46:49 +1100\r\n"
1408    "From: yme@false.com\r\n"
1409    "To: you\r\n"
1410    "Subject: simple address test\r\n"
1411    "\r\n"
1412    "blah\n"
1413    ;
1414    sieve_test_context_t ctx;
1415
1416    context_setup(&ctx, SCRIPT);
1417    CU_ASSERT_EQUAL(ctx.stats.errors, 0);
1418
1419    run_message(&ctx, MSG_TRUE);
1420    CU_ASSERT_EQUAL(ctx.stats.errors, 0);
1421    CU_ASSERT_EQUAL(ctx.stats.actions, 1);
1422    CU_ASSERT_EQUAL(ctx.stats.redirects, 1);
1423    CU_ASSERT_EQUAL(ctx.stats.keeps, 0);
1424    CU_ASSERT_STRING_EQUAL(ctx.redirected_to, "me@blah.com");
1425
1426    run_message(&ctx, MSG_FALSE);
1427    CU_ASSERT_EQUAL(ctx.stats.errors, 0);
1428    CU_ASSERT_EQUAL(ctx.stats.actions, 2);
1429    CU_ASSERT_EQUAL(ctx.stats.redirects, 1);
1430    CU_ASSERT_EQUAL(ctx.stats.keeps, 1);
1431    CU_ASSERT_STRING_EQUAL(ctx.redirected_to, "me@blah.com");
1432
1433    context_cleanup(&ctx);
1434}
1435
1436static void test_date_zone(void)
1437{
1438    static const char SCRIPT[] =
1439    "require [\"date\"];\n"
1440    "if date :is :originalzone \"date\" \"zone\" \"+1100\"\n"
1441    "{redirect \"me@blah.com\";}\n"
1442    ;
1443    static const char MSG_TRUE[] =
1444    "Date: Sat, 2 Nov 2013 19:46:49 +1100\r\n"
1445    "From: zme@true.com\r\n"
1446    "To: you\r\n"
1447    "Subject: simple address test\r\n"
1448    "\r\n"
1449    "blah\n"
1450    ;
1451    static const char MSG_FALSE[] =
1452    "Date: Fri, 1 Nov 2013 11:46:49 -0700\r\n"
1453    "From: yme@false.com\r\n"
1454    "To: you\r\n"
1455    "Subject: simple address test\r\n"
1456    "\r\n"
1457    "blah\n"
1458    ;
1459    sieve_test_context_t ctx;
1460
1461    context_setup(&ctx, SCRIPT);
1462    CU_ASSERT_EQUAL(ctx.stats.errors, 0);
1463
1464    run_message(&ctx, MSG_TRUE);
1465    CU_ASSERT_EQUAL(ctx.stats.errors, 0);
1466    CU_ASSERT_EQUAL(ctx.stats.actions, 1);
1467    CU_ASSERT_EQUAL(ctx.stats.redirects, 1);
1468    CU_ASSERT_EQUAL(ctx.stats.keeps, 0);
1469    CU_ASSERT_STRING_EQUAL(ctx.redirected_to, "me@blah.com");
1470
1471    run_message(&ctx, MSG_FALSE);
1472    CU_ASSERT_EQUAL(ctx.stats.errors, 0);
1473    CU_ASSERT_EQUAL(ctx.stats.actions, 2);
1474    CU_ASSERT_EQUAL(ctx.stats.redirects, 1);
1475    CU_ASSERT_EQUAL(ctx.stats.keeps, 1);
1476    CU_ASSERT_STRING_EQUAL(ctx.redirected_to, "me@blah.com");
1477
1478    context_cleanup(&ctx);
1479}
1480
1481static void test_currentdate_true(void)
1482{
1483    static const char SCRIPT[] =
1484    "require [\"date\", \"relational\"];\n"
1485    "if allof(currentdate :zone \"+0000\" :value \"ge\" \"date\" \"2014-01-01\",\n"
1486    "         currentdate :zone \"+0000\" :value \"le\" \"date\" \"2114-01-01\")\n"
1487    "{redirect \"me@blah.com\";}\n"
1488    ;
1489    static const char MSG_TRUE[] =
1490    "Date: Tue, 16 Nov 2010 12:46:49 +1100\r\n"
1491    "From: yme@true.com\r\n"
1492    "To: you\r\n"
1493    "Subject: simple address test\r\n"
1494    "\r\n"
1495    "blah\n"
1496    ;
1497    sieve_test_context_t ctx;
1498
1499    context_setup(&ctx, SCRIPT);
1500    CU_ASSERT_EQUAL(ctx.stats.errors, 0);
1501
1502    run_message(&ctx, MSG_TRUE);
1503    CU_ASSERT_EQUAL(ctx.stats.errors, 0);
1504    CU_ASSERT_EQUAL(ctx.stats.actions, 1);
1505    CU_ASSERT_EQUAL(ctx.stats.redirects, 1);
1506    CU_ASSERT_EQUAL(ctx.stats.keeps, 0);
1507    CU_ASSERT_STRING_EQUAL(ctx.redirected_to, "me@blah.com");
1508
1509    context_cleanup(&ctx);
1510}
1511
1512static void test_currentdate_false(void)
1513{
1514    static const char SCRIPT[] =
1515    "require [\"date\", \"relational\"];\n"
1516    "if allof(currentdate :zone \"+0000\" :value \"ge\" \"date\" \"1970-01-01\",\n"
1517    "         currentdate :zone \"+0000\" :value \"le\" \"date\" \"2014-01-01\")\n"
1518    "{redirect \"me@blah.com\";}\n"
1519    ;
1520    static const char MSG_FALSE[] =
1521    "Date: Tue, 16 Nov 2010 12:46:49 +1100\r\n"
1522    "From: yme@false.com\r\n"
1523    "To: you\r\n"
1524    "Subject: simple address test\r\n"
1525    "\r\n"
1526    "blah\n"
1527    ;
1528    sieve_test_context_t ctx;
1529
1530    context_setup(&ctx, SCRIPT);
1531    CU_ASSERT_EQUAL(ctx.stats.errors, 0);
1532
1533    run_message(&ctx, MSG_FALSE);
1534    CU_ASSERT_EQUAL(ctx.stats.errors, 0);
1535    CU_ASSERT_EQUAL(ctx.stats.actions, 1);
1536    CU_ASSERT_EQUAL(ctx.stats.redirects, 0);
1537    CU_ASSERT_EQUAL(ctx.stats.keeps, 1);
1538    CU_ASSERT_STRING_EQUAL(ctx.redirected_to, NULL);
1539
1540    context_cleanup(&ctx);
1541}
1542
1543static void test_imap4flags_setflag(void)
1544{
1545    static const char SCRIPT_INTERNAL[] =
1546    "require [\"imap4flags\", \"variables\"];\n"
1547    "setflag \"myvar\" [\"myval1\", \"myval2\"];\n"
1548    "keep :flags \"${myvar}\";\n"
1549    ;
1550
1551    static const char MSG_TRUE[] =
1552    "Date: Mon, 25 Jan 2003 08:51:06 -0500\r\n"
1553    "From: zme@true.com\r\n"
1554    "To: you\r\n"
1555    "Subject: simple flag test\r\n"
1556    "\r\n"
1557    "blah\n"
1558    ;
1559    sieve_test_context_t ctx;
1560
1561    /* Test setflag "variablename" "flags" */
1562    context_setup(&ctx, SCRIPT_INTERNAL);
1563    CU_ASSERT_EQUAL(ctx.stats.errors, 0);
1564
1565    run_message(&ctx, MSG_TRUE);
1566    CU_ASSERT_EQUAL(ctx.stats.errors, 0);
1567    CU_ASSERT_EQUAL(ctx.stats.actions, 1);
1568    CU_ASSERT_EQUAL(ctx.stats.keeps, 1);
1569    CU_ASSERT_NOT_EQUAL(strarray_find_case(ctx.flags,"myval1",0),-1);
1570    CU_ASSERT_NOT_EQUAL(strarray_find_case(ctx.flags,"myval2",0),-1);
1571
1572    context_cleanup(&ctx);
1573
1574    /* Test #2 */
1575}
1576
1577// TODO: test
1578// if size :over 10K { redirect "me@blah.com"; }
1579// TODO: test
1580// if true {...}
1581//
1582// if false {...}
1583//
1584// if not false {...}
1585//
1586// if true {...} else {...}
1587//
1588// if false {...} elsif true {...} else {...}
1589//
1590// if false {...} elsif false {...} else {...}
1591//
1592// if false {} else {...}
1593//
1594// if true { if true { if true { ... } } }
1595//
1596// if allof(false, false) {...} else {...}
1597//
1598// if allof(false,true) {...} else {...}
1599//
1600// if allof(true,false) {...} else {...}
1601//
1602// if allof(true,true) {...} else {...}
1603//
1604// if anyof(false, false) {...} else {...}
1605//
1606// if anyof(false,true) {...} else {...}
1607//
1608// if anyof(true,false) {...} else {...}
1609//
1610// if anyof(true,true) {...} else {...}
1611/* vim: set ft=c: */
1612