• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

neomutt / neomutt / 20704611763

04 Jan 2026 11:45AM UTC coverage: 45.271% (+1.1%) from 44.194%
20704611763

push

github

flatcap
notmuch: restore virtual-mailboxes

`virtual-mailboxes` has been dropped from the docs.

The preferred commands are:

- `mailboxes -label LABEL MAILBOX-PATH`
- `named-mailboxes LABEL MAILBOX-PATH`

11367 of 25109 relevant lines covered (45.27%)

291.36 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/pattern/exec.c
1
/**
2
 * @file
3
 * Execute a Pattern
4
 *
5
 * @authors
6
 * Copyright (C) 2020 Romeu Vieira <romeu.bizz@gmail.com>
7
 * Copyright (C) 2020-2024 Richard Russon <rich@flatcap.org>
8
 * Copyright (C) 2021-2023 Pietro Cerutti <gahr@gahr.ch>
9
 * Copyright (C) 2023 Leon Philman
10
 * Copyright (C) 2024 Dennis Schön <mail@dennis-schoen.de>
11
 * Copyright (C) 2025 Alejandro Colomar <alx@kernel.org>
12
 *
13
 * @copyright
14
 * This program is free software: you can redistribute it and/or modify it under
15
 * the terms of the GNU General Public License as published by the Free Software
16
 * Foundation, either version 2 of the License, or (at your option) any later
17
 * version.
18
 *
19
 * This program is distributed in the hope that it will be useful, but WITHOUT
20
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
21
 * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
22
 * details.
23
 *
24
 * You should have received a copy of the GNU General Public License along with
25
 * this program.  If not, see <http://www.gnu.org/licenses/>.
26
 */
27

28
/**
29
 * @page pattern_exec Execute a Pattern
30
 *
31
 * Execute a Pattern
32
 */
33

34
#include "config.h"
35
#include <stdarg.h> // IWYU pragma: keep
36
#include <stdbool.h>
37
#include <stdio.h>
38
#include <string.h>
39
#include <unistd.h>
40
#include "private.h"
41
#include "mutt/lib.h"
42
#include "address/lib.h"
43
#include "config/lib.h"
44
#include "email/lib.h"
45
#include "core/lib.h"
46
#include "alias/alias.h" // IWYU pragma: keep
47
#include "alias/gui.h"   // IWYU pragma: keep
48
#include "alias/lib.h"
49
#include "mutt.h"
50
#include "lib.h"
51
#include "attach/lib.h"
52
#include "ncrypt/lib.h"
53
#include "send/lib.h"
54
#include "copy.h"
55
#include "handler.h"
56
#include "maillist.h"
57
#include "mx.h"
58
#ifndef USE_FMEMOPEN
59
#include <sys/stat.h>
60
#endif
61

62
static bool pattern_exec(struct Pattern *pat, PatternExecFlags flags,
63
                         struct Mailbox *m, struct Email *e,
64
                         struct Message *msg, struct PatternCache *cache);
65

66
/**
67
 * patmatch - Compare a string to a Pattern
68
 * @param pat Pattern to use
69
 * @param buf String to compare
70
 * @retval true  Match
71
 * @retval false No match
72
 */
73
static bool patmatch(const struct Pattern *pat, const char *buf)
×
74
{
75
  if (pat->is_multi)
×
76
    return (mutt_list_find(&pat->p.multi_cases, buf) != NULL);
×
77
  if (pat->string_match)
×
78
    return pat->ign_case ? mutt_istr_find(buf, pat->p.str) : strstr(buf, pat->p.str);
×
79
  if (pat->group_match)
×
80
    return group_match(pat->p.group, buf);
×
81
  return (regexec(pat->p.regex, buf, 0, NULL, 0) == 0);
×
82
}
83

84
/**
85
 * print_crypt_pattern_op_error - Print an error for a disabled crypto pattern
86
 * @param op Operation, e.g. #MUTT_PAT_CRYPT_SIGN
87
 */
88
static void print_crypt_pattern_op_error(int op)
89
{
90
  const struct PatternFlags *entry = lookup_op(op);
91
  if (entry)
92
  {
93
    /* L10N: One of the crypt pattern operators: ~g, ~G, ~k, ~V
94
       was invoked when NeoMutt was compiled without crypto support.
95
       %c is the pattern character, i.e. "g".  */
96
    mutt_error(_("Pattern operator '~%c' is disabled"), entry->tag);
97
  }
98
  else
99
  {
100
    /* L10N: An unknown pattern operator was somehow invoked.
101
       This shouldn't be possible unless there is a bug.  */
102
    mutt_error(_("error: unknown op %d (report this error)"), op);
103
  }
104
}
105

106
/**
107
 * msg_search - Search an email
108
 * @param pat   Pattern to find
109
 * @param e   Email
110
 * @param msg Message
111
 * @retval true Pattern found
112
 * @retval false Error or pattern not found
113
 */
114
static bool msg_search(struct Pattern *pat, struct Email *e, struct Message *msg)
×
115
{
116
  ASSERT(msg);
×
117

118
  bool match = false;
119

120
  FILE *fp = NULL;
×
121
  long len = 0;
122
#ifdef USE_FMEMOPEN
123
  char *temp = NULL;
124
  size_t tempsize = 0;
125
#else
126
  struct stat st = { 0 };
×
127
#endif
128

129
  const bool needs_head = (pat->op == MUTT_PAT_HEADER) || (pat->op == MUTT_PAT_WHOLE_MSG);
×
130
  const bool needs_body = (pat->op == MUTT_PAT_BODY) || (pat->op == MUTT_PAT_WHOLE_MSG);
×
131
  const bool c_thorough_search = cs_subset_bool(NeoMutt->sub, "thorough_search");
×
132
  if (c_thorough_search)
×
133
  {
134
    /* decode the header / body */
135
    struct State state = { 0 };
×
136
    state.fp_in = msg->fp;
×
137
    state.flags = STATE_CHARCONV;
×
138
#ifdef USE_FMEMOPEN
139
    state.fp_out = open_memstream(&temp, &tempsize);
140
    if (!state.fp_out)
141
    {
142
      mutt_perror(_("Error opening 'memory stream'"));
143
      return false;
144
    }
145
#else
146
    state.fp_out = mutt_file_mkstemp();
×
147
    if (!state.fp_out)
×
148
    {
149
      mutt_perror(_("Can't create temporary file"));
×
150
      return false;
×
151
    }
152
#endif
153

154
    if (needs_head)
×
155
    {
156
      mutt_copy_header(msg->fp, e, state.fp_out, CH_FROM | CH_DECODE, NULL, 0);
×
157
    }
158

159
    if (needs_body)
×
160
    {
161
      mutt_parse_mime_message(e, msg->fp);
×
162

163
      if ((WithCrypto != 0) && (e->security & SEC_ENCRYPT) &&
×
164
          !crypt_valid_passphrase(e->security))
×
165
      {
166
        if (state.fp_out)
×
167
        {
168
          mutt_file_fclose(&state.fp_out);
×
169
#ifdef USE_FMEMOPEN
170
          FREE(&temp);
171
#endif
172
        }
173
        return false;
×
174
      }
175

176
      if (!mutt_file_seek(msg->fp, e->offset, SEEK_SET))
×
177
      {
178
#ifdef USE_FMEMOPEN
179
        FREE(&temp);
180
#endif
181
        mutt_file_fclose(&state.fp_out);
×
182
        return false;
×
183
      }
184
      mutt_body_handler(e->body, &state);
×
185
    }
186

187
#ifdef USE_FMEMOPEN
188
    mutt_file_fclose(&state.fp_out);
189
    len = tempsize;
190

191
    if (tempsize != 0)
192
    {
193
      fp = fmemopen(temp, tempsize, "r");
194
      if (!fp)
195
      {
196
        mutt_perror(_("Error re-opening 'memory stream'"));
197
        FREE(&temp);
198
        return false;
199
      }
200
    }
201
    else
202
    { /* fmemopen can't handle empty buffers */
203
      fp = mutt_file_fopen("/dev/null", "r");
204
      if (!fp)
205
      {
206
        mutt_perror(_("Error opening /dev/null"));
207
        FREE(&temp);
208
        return false;
209
      }
210
    }
211
#else
212
    fp = state.fp_out;
×
213
    fflush(fp);
×
214
    if (!mutt_file_seek(fp, 0, SEEK_SET) || fstat(fileno(fp), &st))
×
215
    {
216
      mutt_perror(_("Error checking length of temporary file"));
×
217
      mutt_file_fclose(&fp);
×
218
      return false;
×
219
    }
220
    len = (long) st.st_size;
×
221
#endif
222
  }
223
  else
224
  {
225
    /* raw header / body */
226
    fp = msg->fp;
×
227
    if (needs_head)
×
228
    {
229
      if (!mutt_file_seek(fp, e->offset, SEEK_SET))
×
230
      {
231
        return false;
232
      }
233
      len = e->body->offset - e->offset;
×
234
    }
235
    if (needs_body)
×
236
    {
237
      if (pat->op == MUTT_PAT_BODY)
×
238
      {
239
        if (!mutt_file_seek(fp, e->body->offset, SEEK_SET))
×
240
        {
241
          return false;
242
        }
243
      }
244
      len += e->body->length;
×
245
    }
246
  }
247

248
  /* search the file "fp" */
249
  if (pat->op == MUTT_PAT_HEADER)
×
250
  {
251
    struct Buffer *buf = buf_pool_get();
×
252
    while (len > 0)
×
253
    {
254
      if (mutt_rfc822_read_line(fp, buf) == 0)
×
255
      {
256
        break;
257
      }
258
      len -= buf_len(buf);
×
259
      if (patmatch(pat, buf_string(buf)))
×
260
      {
261
        match = true;
262
        break;
263
      }
264
    }
265
    buf_pool_release(&buf);
×
266
  }
267
  else
268
  {
269
    char buf[1024] = { 0 };
×
270
    while (len > 0)
×
271
    {
272
      if (!fgets(buf, sizeof(buf), fp))
×
273
      {
274
        break; /* don't loop forever */
275
      }
276
      len -= mutt_str_len(buf);
×
277
      if (patmatch(pat, buf))
×
278
      {
279
        match = true;
280
        break;
281
      }
282
    }
283
  }
284

285
  if (c_thorough_search)
×
286
    mutt_file_fclose(&fp);
×
287

288
#ifdef USE_FMEMOPEN
289
  FREE(&temp);
290
#endif
291

292
  return match;
293
}
294

295
/**
296
 * perform_and - Perform a logical AND on a set of Patterns
297
 * @param pat   Patterns to test
298
 * @param flags Optional flags, e.g. #MUTT_MATCH_FULL_ADDRESS
299
 * @param m   Mailbox
300
 * @param e   Email
301
 * @param msg Message
302
 * @param cache Cached Patterns
303
 * @retval true ALL of the Patterns evaluates to true
304
 */
305
static bool perform_and(struct PatternList *pat, PatternExecFlags flags,
×
306
                        struct Mailbox *m, struct Email *e, struct Message *msg,
307
                        struct PatternCache *cache)
308
{
309
  struct Pattern *p = NULL;
310

311
  SLIST_FOREACH(p, pat, entries)
×
312
  {
313
    if (!pattern_exec(p, flags, m, e, msg, cache))
×
314
    {
315
      return false;
316
    }
317
  }
318
  return true;
319
}
320

321
/**
322
 * perform_alias_and - Perform a logical AND on a set of Patterns
323
 * @param pat   Patterns to test
324
 * @param flags Optional flags, e.g. #MUTT_MATCH_FULL_ADDRESS
325
 * @param av    AliasView
326
 * @param cache Cached Patterns
327
 * @retval true ALL of the Patterns evaluate to true
328
 */
329
static bool perform_alias_and(struct PatternList *pat, PatternExecFlags flags,
×
330
                              struct AliasView *av, struct PatternCache *cache)
331
{
332
  struct Pattern *p = NULL;
333

334
  SLIST_FOREACH(p, pat, entries)
×
335
  {
336
    if (!mutt_pattern_alias_exec(p, flags, av, cache))
×
337
    {
338
      return false;
339
    }
340
  }
341
  return true;
342
}
343

344
/**
345
 * perform_or - Perform a logical OR on a set of Patterns
346
 * @param pat   Patterns to test
347
 * @param flags Optional flags, e.g. #MUTT_MATCH_FULL_ADDRESS
348
 * @param m   Mailbox
349
 * @param e   Email
350
 * @param msg Message
351
 * @param cache Cached Patterns
352
 * @retval true ONE (or more) of the Patterns evaluates to true
353
 */
354
static int perform_or(struct PatternList *pat, PatternExecFlags flags,
×
355
                      struct Mailbox *m, struct Email *e, struct Message *msg,
356
                      struct PatternCache *cache)
357
{
358
  struct Pattern *p = NULL;
359

360
  SLIST_FOREACH(p, pat, entries)
×
361
  {
362
    if (pattern_exec(p, flags, m, e, msg, cache))
×
363
    {
364
      return true;
365
    }
366
  }
367
  return false;
368
}
369

370
/**
371
 * perform_alias_or - Perform a logical OR on a set of Patterns
372
 * @param pat   Patterns to test
373
 * @param flags Optional flags, e.g. #MUTT_MATCH_FULL_ADDRESS
374
 * @param av    AliasView
375
 * @param cache Cached Patterns
376
 * @retval true ONE (or more) of the Patterns evaluates to true
377
 */
378
static int perform_alias_or(struct PatternList *pat, PatternExecFlags flags,
×
379
                            struct AliasView *av, struct PatternCache *cache)
380
{
381
  struct Pattern *p = NULL;
382

383
  SLIST_FOREACH(p, pat, entries)
×
384
  {
385
    if (mutt_pattern_alias_exec(p, flags, av, cache))
×
386
    {
387
      return true;
388
    }
389
  }
390
  return false;
391
}
392

393
/**
394
 * match_tags - match a pattern against a tags list
395
 * @param pat  pattern to find
396
 * @param tags tags list
397
 * @retval true if any tag match
398
 */
399
static bool match_tags(struct Pattern *pat, struct TagList *tags)
×
400
{
401
  struct Tag *tag = NULL;
402
  bool matched = false;
403
  STAILQ_FOREACH(tag, tags, entries)
×
404
  {
405
    matched |= patmatch(pat, tag->name);
×
406
  }
407
  return pat->pat_not ^ matched;
×
408
}
409

410
/**
411
 * match_addrlist - match a pattern against an address list
412
 * @param pat            pattern to find
413
 * @param match_personal if true, also match the pattern against the real name
414
 * @param n              number of addresses supplied
415
 * @param ...            variable number of addresses
416
 * @retval true
417
 * - one address matches (all_addr is false)
418
 * - all the addresses match (all_addr is true)
419
 */
420
static int match_addrlist(struct Pattern *pat, bool match_personal, int n, ...)
×
421
{
422
  va_list ap;
423

424
  va_start(ap, n);
×
425
  while (n-- > 0)
×
426
  {
427
    struct AddressList *al = va_arg(ap, struct AddressList *);
×
428
    struct Address *a = NULL;
429
    TAILQ_FOREACH(a, al, entries)
×
430
    {
431
      if (pat->all_addr ^
×
432
          ((!pat->is_alias || alias_reverse_lookup(a)) &&
×
433
           ((a->mailbox && patmatch(pat, buf_string(a->mailbox))) ||
×
434
            (match_personal && a->personal && patmatch(pat, buf_string(a->personal))))))
×
435
      {
436
        va_end(ap);
×
437
        return !pat->all_addr; /* Found match, or non-match if all_addr */
×
438
      }
439
    }
440
  }
441
  va_end(ap);
×
442
  return pat->all_addr; /* No matches, or all matches if all_addr */
×
443
}
444

445
/**
446
 * match_reference - Match references against a Pattern
447
 * @param pat  Pattern to match
448
 * @param refs List of References
449
 * @retval true One of the references matches
450
 */
451
static bool match_reference(struct Pattern *pat, struct ListHead *refs)
×
452
{
453
  struct ListNode *np = NULL;
454
  STAILQ_FOREACH(np, refs, entries)
×
455
  {
456
    if (patmatch(pat, np->data))
×
457
      return true;
458
  }
459
  return false;
460
}
461

462
/**
463
 * mutt_is_predicate_recipient - Test an Envelopes Addresses using a predicate function
464
 * @param all_addr If true, ALL Addresses must match
465
 * @param env     Envelope
466
 * @param p       Predicate function, e.g. mutt_is_subscribed_list()
467
 * @retval true
468
 * - One Address matches (all_addr is false)
469
 * - All the Addresses match (all_addr is true)
470
 *
471
 * Test the 'To' and 'Cc' fields of an Address using a test function (the predicate).
472
 */
473
static bool mutt_is_predicate_recipient(bool all_addr, struct Envelope *env, addr_predicate_t p)
×
474
{
475
  struct AddressList *als[] = { &env->to, &env->cc };
×
476
  for (size_t i = 0; i < countof(als); i++)
×
477
  {
478
    struct AddressList *al = als[i];
×
479
    struct Address *a = NULL;
480
    TAILQ_FOREACH(a, al, entries)
×
481
    {
482
      if (all_addr ^ p(a))
×
483
        return !all_addr;
×
484
    }
485
  }
486
  return all_addr;
487
}
488

489
/**
490
 * mutt_is_subscribed_list_recipient - Matches subscribed mailing lists
491
 * @param all_addr If true, ALL Addresses must be on the subscribed list
492
 * @param env     Envelope
493
 * @retval true
494
 * - One Address is subscribed (all_addr is false)
495
 * - All the Addresses are subscribed (all_addr is true)
496
 */
497
bool mutt_is_subscribed_list_recipient(bool all_addr, struct Envelope *env)
×
498
{
499
  return mutt_is_predicate_recipient(all_addr, env, &mutt_is_subscribed_list);
×
500
}
501

502
/**
503
 * mutt_is_list_recipient - Matches known mailing lists
504
 * @param all_addr If true, ALL Addresses must be mailing lists
505
 * @param env     Envelope
506
 * @retval true
507
 * - One Address is a mailing list (all_addr is false)
508
 * - All the Addresses are mailing lists (all_addr is true)
509
 */
510
bool mutt_is_list_recipient(bool all_addr, struct Envelope *env)
×
511
{
512
  return mutt_is_predicate_recipient(all_addr, env, &mutt_is_mail_list);
×
513
}
514

515
/**
516
 * match_user - Matches the user's email Address
517
 * @param all_addr If true, ALL Addresses must refer to the user
518
 * @param n       number of AddressLists supplied
519
 * @param ...     Variable number of AddressLists
520
 * @retval true
521
 * - One Address refers to the user (all_addr is false)
522
 * - All the Addresses refer to the user (all_addr is true)
523
 */
524
static int match_user(bool all_addr, int n, ...)
×
525
{
526
  va_list ap;
527

528
  va_start(ap, n);
×
529
  while (n-- > 0)
×
530
  {
531
    struct AddressList *al = va_arg(ap, struct AddressList *);
×
532
    struct Address *a = NULL;
533
    TAILQ_FOREACH(a, al, entries)
×
534
    {
535
      if (all_addr ^ mutt_addr_is_user(a))
×
536
      {
537
        va_end(ap);
×
538
        return !all_addr;
×
539
      }
540
    }
541
  }
542
  va_end(ap);
×
543
  return all_addr;
×
544
}
545

546
/**
547
 * match_threadcomplete - Match a Pattern against an email thread
548
 * @param pat   Pattern to match
549
 * @param flags Flags, e.g. #MUTT_MATCH_FULL_ADDRESS
550
 * @param m   Mailbox
551
 * @param t     Email thread
552
 * @param left  Navigate to the previous email
553
 * @param up    Navigate to the email's parent
554
 * @param right Navigate to the next email
555
 * @param down  Navigate to the email's children
556
 * @retval 1  Success, match found
557
 * @retval 0  No match
558
 */
559
static int match_threadcomplete(struct PatternList *pat, PatternExecFlags flags,
×
560
                                struct Mailbox *m, struct MuttThread *t,
561
                                int left, int up, int right, int down)
562
{
563
  if (!t)
×
564
    return 0;
565

566
  int a;
567
  struct Email *e = t->message;
×
568
  if (e)
×
569
    if (mutt_pattern_exec(SLIST_FIRST(pat), flags, m, e, NULL))
×
570
      return 1;
571

572
  if (up && (a = match_threadcomplete(pat, flags, m, t->parent, 1, 1, 1, 0)))
×
573
    return a;
574
  if (right && t->parent && (a = match_threadcomplete(pat, flags, m, t->next, 0, 0, 1, 1)))
×
575
  {
576
    return a;
577
  }
578
  if (left && t->parent && (a = match_threadcomplete(pat, flags, m, t->prev, 1, 0, 0, 1)))
×
579
  {
580
    return a;
581
  }
582
  if (down && (a = match_threadcomplete(pat, flags, m, t->child, 1, 0, 1, 1)))
×
583
    return a;
584
  return 0;
585
}
586

587
/**
588
 * match_threadparent - Match Pattern against an email's parent
589
 * @param pat   Pattern to match
590
 * @param flags Flags, e.g. #MUTT_MATCH_FULL_ADDRESS
591
 * @param m   Mailbox
592
 * @param t     Thread of email
593
 * @retval  1 Success, pattern matched
594
 * @retval  0 Pattern did not match
595
 * @retval -1 Error
596
 */
597
static int match_threadparent(struct PatternList *pat, PatternExecFlags flags,
×
598
                              struct Mailbox *m, struct MuttThread *t)
599
{
600
  if (!t || !t->parent || !t->parent->message)
×
601
    return 0;
602

603
  return mutt_pattern_exec(SLIST_FIRST(pat), flags, m, t->parent->message, NULL);
×
604
}
605

606
/**
607
 * match_threadchildren - Match Pattern against an email's children
608
 * @param pat   Pattern to match
609
 * @param flags Flags, e.g. #MUTT_MATCH_FULL_ADDRESS
610
 * @param m   Mailbox
611
 * @param t     Thread of email
612
 * @retval  1 Success, pattern matched
613
 * @retval  0 Pattern did not match
614
 * @retval -1 Error
615
 */
616
static int match_threadchildren(struct PatternList *pat, PatternExecFlags flags,
×
617
                                struct Mailbox *m, struct MuttThread *t)
618
{
619
  if (!t || !t->child)
×
620
    return 0;
621

622
  for (t = t->child; t; t = t->next)
×
623
    if (t->message && mutt_pattern_exec(SLIST_FIRST(pat), flags, m, t->message, NULL))
×
624
      return 1;
625

626
  return 0;
627
}
628

629
/**
630
 * match_content_type - Match a Pattern against an Attachment's Content-Type
631
 * @param pat   Pattern to match
632
 * @param b     Attachment
633
 * @retval true  Success, pattern matched
634
 * @retval false Pattern did not match
635
 */
636
static bool match_content_type(const struct Pattern *pat, struct Body *b)
×
637
{
638
  if (!b)
×
639
    return false;
640

641
  char buf[256] = { 0 };
×
642
  snprintf(buf, sizeof(buf), "%s/%s", BODY_TYPE(b), b->subtype);
×
643

644
  if (patmatch(pat, buf))
×
645
    return true;
646
  if (match_content_type(pat, b->parts))
×
647
    return true;
648
  if (match_content_type(pat, b->next))
×
649
    return true;
650
  return false;
651
}
652

653
/**
654
 * match_mime_content_type - Match a Pattern against an email's Content-Type
655
 * @param pat Pattern to match
656
 * @param e   Email
657
 * @param fp  Message file
658
 * @retval true  Success, pattern matched
659
 * @retval false Pattern did not match
660
 */
661
static bool match_mime_content_type(const struct Pattern *pat, struct Email *e, FILE *fp)
×
662
{
663
  mutt_parse_mime_message(e, fp);
×
664
  return match_content_type(pat, e->body);
×
665
}
666

667
/**
668
 * match_update_dynamic_date - Update a dynamic date pattern
669
 * @param pat Pattern to modify
670
 * @retval true  Pattern valid and updated
671
 * @retval false Pattern invalid
672
 */
673
static bool match_update_dynamic_date(struct Pattern *pat)
×
674
{
675
  struct Buffer *err = buf_pool_get();
×
676

677
  bool rc = eval_date_minmax(pat, pat->p.str, err);
×
678
  buf_pool_release(&err);
×
679

680
  return rc;
×
681
}
682

683
/**
684
 * set_pattern_cache_value - Sets a value in the PatternCache cache entry
685
 * @param cache_entry Cache entry to update
686
 * @param value       Value to set
687
 *
688
 * Normalizes the "true" value to 2.
689
 */
690
static void set_pattern_cache_value(int *cache_entry, int value)
691
{
692
  *cache_entry = (value != 0) ? 2 : 1;
×
693
}
×
694

695
/**
696
 * get_pattern_cache_value - Get pattern cache value
697
 * @param cache_entry Cache entry to get
698
 * @retval 1 The cache value is set and has a true value
699
 * @retval 0 otherwise (even if unset!)
700
 */
701
static bool get_pattern_cache_value(int cache_entry)
702
{
703
  return cache_entry == 2;
×
704
}
705

706
/**
707
 * is_pattern_cache_set - Is a given Pattern cached?
708
 * @param cache_entry Cache entry to check
709
 * @retval true Pattern is cached
710
 */
711
static int is_pattern_cache_set(int cache_entry)
712
{
713
  return cache_entry != 0;
714
}
715

716
/**
717
 * msg_search_sendmode - Search in send-mode
718
 * @param e   Email to search
719
 * @param pat Pattern to find
720
 * @retval  1 Success, pattern matched
721
 * @retval  0 Pattern did not match
722
 * @retval -1 Error
723
 */
724
static int msg_search_sendmode(struct Email *e, struct Pattern *pat)
×
725
{
726
  bool match = false;
727
  char *buf = NULL;
×
728
  size_t blen = 0;
×
729
  FILE *fp = NULL;
×
730

731
  if ((pat->op == MUTT_PAT_HEADER) || (pat->op == MUTT_PAT_WHOLE_MSG))
×
732
  {
733
    struct Buffer *tempfile = buf_pool_get();
×
734
    buf_mktemp(tempfile);
×
735
    fp = mutt_file_fopen(buf_string(tempfile), "w+");
×
736
    if (!fp)
×
737
    {
738
      mutt_perror("%s", buf_string(tempfile));
×
739
      buf_pool_release(&tempfile);
×
740
      return 0;
×
741
    }
742

743
    mutt_rfc822_write_header(fp, e->env, e->body, MUTT_WRITE_HEADER_POSTPONE,
×
744
                             false, false, NeoMutt->sub);
×
745
    fflush(fp);
×
746
    if (mutt_file_seek(fp, 0, SEEK_SET))
×
747
    {
748
      while ((buf = mutt_file_read_line(buf, &blen, fp, NULL, MUTT_RL_NO_FLAGS)) != NULL)
×
749
      {
750
        if (patmatch(pat, buf) == 0)
×
751
        {
752
          match = true;
753
          break;
754
        }
755
      }
756
    }
757

758
    FREE(&buf);
×
759
    mutt_file_fclose(&fp);
×
760
    unlink(buf_string(tempfile));
×
761
    buf_pool_release(&tempfile);
×
762

763
    if (match)
×
764
      return match;
765
  }
766

767
  if ((pat->op == MUTT_PAT_BODY) || (pat->op == MUTT_PAT_WHOLE_MSG))
×
768
  {
769
    fp = mutt_file_fopen(e->body->filename, "r");
×
770
    if (!fp)
×
771
    {
772
      mutt_perror("%s", e->body->filename);
×
773
      return 0;
×
774
    }
775

776
    while ((buf = mutt_file_read_line(buf, &blen, fp, NULL, MUTT_RL_NO_FLAGS)) != NULL)
×
777
    {
778
      if (patmatch(pat, buf) == 0)
×
779
      {
780
        match = true;
781
        break;
782
      }
783
    }
784

785
    FREE(&buf);
×
786
    mutt_file_fclose(&fp);
×
787
  }
788

789
  return match;
×
790
}
791

792
/**
793
 * pattern_needs_msg - Check whether a pattern needs a full message
794
 * @param m Mailbox
795
 * @param pat Pattern
796
 * @retval true The pattern needs a full message
797
 * @retval false The pattern does not need a full message
798
 */
799
static bool pattern_needs_msg(const struct Mailbox *m, const struct Pattern *pat)
×
800
{
801
  if (!m)
×
802
  {
803
    return false;
804
  }
805

806
  if ((pat->op == MUTT_PAT_MIMETYPE) || (pat->op == MUTT_PAT_MIMEATTACH))
×
807
  {
808
    return true;
809
  }
810

811
  if ((pat->op == MUTT_PAT_WHOLE_MSG) || (pat->op == MUTT_PAT_BODY) || (pat->op == MUTT_PAT_HEADER))
×
812
  {
813
    return !((m->type == MUTT_IMAP) && pat->string_match);
×
814
  }
815

816
  if ((pat->op == MUTT_PAT_AND) || (pat->op == MUTT_PAT_OR))
×
817
  {
818
    struct Pattern *p = NULL;
819
    SLIST_FOREACH(p, pat->child, entries)
×
820
    {
821
      if (pattern_needs_msg(m, p))
×
822
      {
823
        return true;
824
      }
825
    }
826
  }
827

828
  return false;
829
}
830

831
/**
832
 * pattern_exec - Match a pattern against an email header
833
 * @param pat   Pattern to match
834
 * @param flags Flags, e.g. #MUTT_MATCH_FULL_ADDRESS
835
 * @param m     Mailbox
836
 * @param e     Email
837
 * @param msg   MEssage
838
 * @param cache Cache for common Patterns
839
 * @retval true Success, pattern matched
840
 * @retval false Pattern did not match
841
 *
842
 * flags: MUTT_MATCH_FULL_ADDRESS: match both personal and machine address
843
 * cache: For repeated matches against the same Header, passing in non-NULL will
844
 *        store some of the cacheable pattern matches in this structure.
845
 */
846
static bool pattern_exec(struct Pattern *pat, PatternExecFlags flags,
×
847
                         struct Mailbox *m, struct Email *e,
848
                         struct Message *msg, struct PatternCache *cache)
849
{
850
  switch (pat->op)
×
851
  {
852
    case MUTT_PAT_AND:
×
853
      return pat->pat_not ^ (perform_and(pat->child, flags, m, e, msg, cache) > 0);
×
854
    case MUTT_PAT_OR:
×
855
      return pat->pat_not ^ (perform_or(pat->child, flags, m, e, msg, cache) > 0);
×
856
    case MUTT_PAT_THREAD:
×
857
      return pat->pat_not ^
×
858
             match_threadcomplete(pat->child, flags, m, e->thread, 1, 1, 1, 1);
×
859
    case MUTT_PAT_PARENT:
×
860
      return pat->pat_not ^ match_threadparent(pat->child, flags, m, e->thread);
×
861
    case MUTT_PAT_CHILDREN:
×
862
      return pat->pat_not ^ match_threadchildren(pat->child, flags, m, e->thread);
×
863
    case MUTT_ALL:
×
864
      return !pat->pat_not;
×
865
    case MUTT_EXPIRED:
×
866
      return pat->pat_not ^ e->expired;
×
867
    case MUTT_SUPERSEDED:
×
868
      return pat->pat_not ^ e->superseded;
×
869
    case MUTT_FLAG:
×
870
      return pat->pat_not ^ e->flagged;
×
871
    case MUTT_TAG:
×
872
      return pat->pat_not ^ e->tagged;
×
873
    case MUTT_NEW:
×
874
      return pat->pat_not ? e->old || e->read : !(e->old || e->read);
×
875
    case MUTT_UNREAD:
×
876
      return pat->pat_not ? e->read : !e->read;
×
877
    case MUTT_REPLIED:
×
878
      return pat->pat_not ^ e->replied;
×
879
    case MUTT_OLD:
×
880
      return pat->pat_not ? (!e->old || e->read) : (e->old && !e->read);
×
881
    case MUTT_READ:
×
882
      return pat->pat_not ^ e->read;
×
883
    case MUTT_DELETED:
×
884
      return pat->pat_not ^ e->deleted;
×
885
    case MUTT_PAT_MESSAGE:
×
886
      return pat->pat_not ^
×
887
             ((email_msgno(e) >= pat->min) && (email_msgno(e) <= pat->max));
×
888
    case MUTT_PAT_DATE:
×
889
      if (pat->dynamic)
×
890
        match_update_dynamic_date(pat);
×
891
      return pat->pat_not ^ ((e->date_sent >= pat->min) && (e->date_sent <= pat->max));
×
892
    case MUTT_PAT_DATE_RECEIVED:
×
893
      if (pat->dynamic)
×
894
        match_update_dynamic_date(pat);
×
895
      return pat->pat_not ^ ((e->received >= pat->min) && (e->received <= pat->max));
×
896
    case MUTT_PAT_BODY:
×
897
    case MUTT_PAT_HEADER:
898
    case MUTT_PAT_WHOLE_MSG:
899
      if (pat->sendmode)
×
900
      {
901
        if (!e->body || !e->body->filename)
×
902
          return false;
903
        return pat->pat_not ^ msg_search_sendmode(e, pat);
×
904
      }
905
      /* m can be NULL in certain cases, such as when replying to a message
906
       * from the attachment menu and the user has a reply-hook using "~e".
907
       * This is also the case when message scoring.  */
908
      if (!m)
×
909
        return false;
910
      /* IMAP search sets e->matched at search compile time */
911
      if ((m->type == MUTT_IMAP) && pat->string_match)
×
912
        return e->matched;
×
913
      return pat->pat_not ^ msg_search(pat, e, msg);
×
914
    case MUTT_PAT_SERVERSEARCH:
×
915
      if (!m)
×
916
        return false;
917
      if (m->type == MUTT_IMAP)
×
918
      {
919
        return (pat->string_match) ? e->matched : false;
×
920
      }
921
      mutt_error(_("error: server custom search only supported with IMAP"));
×
922
      return false;
×
923
    case MUTT_PAT_SENDER:
×
924
      if (!e->env)
×
925
        return false;
926
      return pat->pat_not ^ match_addrlist(pat, (flags & MUTT_MATCH_FULL_ADDRESS),
×
927
                                           1, &e->env->sender);
928
    case MUTT_PAT_FROM:
×
929
      if (!e->env)
×
930
        return false;
931
      return pat->pat_not ^
×
932
             match_addrlist(pat, (flags & MUTT_MATCH_FULL_ADDRESS), 1, &e->env->from);
×
933
    case MUTT_PAT_TO:
×
934
      if (!e->env)
×
935
        return false;
936
      return pat->pat_not ^
×
937
             match_addrlist(pat, (flags & MUTT_MATCH_FULL_ADDRESS), 1, &e->env->to);
×
938
    case MUTT_PAT_CC:
×
939
      if (!e->env)
×
940
        return false;
941
      return pat->pat_not ^
×
942
             match_addrlist(pat, (flags & MUTT_MATCH_FULL_ADDRESS), 1, &e->env->cc);
×
943
    case MUTT_PAT_BCC:
×
944
      if (!e->env)
×
945
        return false;
946
      return pat->pat_not ^
×
947
             match_addrlist(pat, (flags & MUTT_MATCH_FULL_ADDRESS), 1, &e->env->bcc);
×
948
    case MUTT_PAT_SUBJECT:
×
949
      if (!e->env)
×
950
        return false;
951
      return pat->pat_not ^ (e->env->subject && patmatch(pat, e->env->subject));
×
952
    case MUTT_PAT_ID:
×
953
    case MUTT_PAT_ID_EXTERNAL:
954
      if (!e->env)
×
955
        return false;
956
      return pat->pat_not ^ (e->env->message_id && patmatch(pat, e->env->message_id));
×
957
    case MUTT_PAT_SCORE:
×
958
      return pat->pat_not ^ (e->score >= pat->min &&
×
959
                             (pat->max == MUTT_MAXRANGE || e->score <= pat->max));
×
960
    case MUTT_PAT_SIZE:
×
961
      return pat->pat_not ^ (e->body->length >= pat->min &&
×
962
                             (pat->max == MUTT_MAXRANGE || e->body->length <= pat->max));
×
963
    case MUTT_PAT_REFERENCE:
×
964
      if (!e->env)
×
965
        return false;
966
      return pat->pat_not ^ (match_reference(pat, &e->env->references) ||
×
967
                             match_reference(pat, &e->env->in_reply_to));
×
968
    case MUTT_PAT_ADDRESS:
×
969
      if (!e->env)
×
970
        return false;
971
      return pat->pat_not ^ match_addrlist(pat, (flags & MUTT_MATCH_FULL_ADDRESS),
×
972
                                           5, &e->env->from, &e->env->sender,
973
                                           &e->env->to, &e->env->cc, &e->env->bcc);
974
    case MUTT_PAT_RECIPIENT:
×
975
      if (!e->env)
×
976
        return false;
977
      return pat->pat_not ^ match_addrlist(pat, (flags & MUTT_MATCH_FULL_ADDRESS), 3,
×
978
                                           &e->env->to, &e->env->cc, &e->env->bcc);
979
    case MUTT_PAT_LIST: /* known list, subscribed or not */
×
980
    {
981
      if (!e->env)
×
982
        return false;
983

984
      bool result;
985
      if (cache)
×
986
      {
987
        int *cache_entry = pat->all_addr ? &cache->list_all : &cache->list_one;
×
988
        if (!is_pattern_cache_set(*cache_entry))
×
989
        {
990
          set_pattern_cache_value(cache_entry,
991
                                  mutt_is_list_recipient(pat->all_addr, e->env));
×
992
        }
993
        result = get_pattern_cache_value(*cache_entry);
×
994
      }
995
      else
996
      {
997
        result = mutt_is_list_recipient(pat->all_addr, e->env);
×
998
      }
999
      return pat->pat_not ^ result;
×
1000
    }
1001
    case MUTT_PAT_SUBSCRIBED_LIST:
×
1002
    {
1003
      if (!e->env)
×
1004
        return false;
1005

1006
      bool result;
1007
      if (cache)
×
1008
      {
1009
        int *cache_entry = pat->all_addr ? &cache->sub_all : &cache->sub_one;
×
1010
        if (!is_pattern_cache_set(*cache_entry))
×
1011
        {
1012
          set_pattern_cache_value(cache_entry,
1013
                                  mutt_is_subscribed_list_recipient(pat->all_addr, e->env));
×
1014
        }
1015
        result = get_pattern_cache_value(*cache_entry);
×
1016
      }
1017
      else
1018
      {
1019
        result = mutt_is_subscribed_list_recipient(pat->all_addr, e->env);
×
1020
      }
1021
      return pat->pat_not ^ result;
×
1022
    }
1023
    case MUTT_PAT_PERSONAL_RECIP:
×
1024
    {
1025
      if (!e->env)
×
1026
        return false;
1027

1028
      bool result;
1029
      if (cache)
×
1030
      {
1031
        int *cache_entry = pat->all_addr ? &cache->pers_recip_all : &cache->pers_recip_one;
×
1032
        if (!is_pattern_cache_set(*cache_entry))
×
1033
        {
1034
          set_pattern_cache_value(cache_entry,
×
1035
                                  match_user(pat->all_addr, 3, &e->env->to,
×
1036
                                             &e->env->cc, &e->env->bcc));
1037
        }
1038
        result = get_pattern_cache_value(*cache_entry);
×
1039
      }
1040
      else
1041
      {
1042
        result = match_user(pat->all_addr, 3, &e->env->to, &e->env->cc, &e->env->bcc);
×
1043
      }
1044
      return pat->pat_not ^ result;
×
1045
    }
1046
    case MUTT_PAT_PERSONAL_FROM:
×
1047
    {
1048
      if (!e->env)
×
1049
        return false;
1050

1051
      bool result;
1052
      if (cache)
×
1053
      {
1054
        int *cache_entry = pat->all_addr ? &cache->pers_from_all : &cache->pers_from_one;
×
1055
        if (!is_pattern_cache_set(*cache_entry))
×
1056
        {
1057
          set_pattern_cache_value(cache_entry,
×
1058
                                  match_user(pat->all_addr, 1, &e->env->from));
×
1059
        }
1060
        result = get_pattern_cache_value(*cache_entry);
×
1061
      }
1062
      else
1063
      {
1064
        result = match_user(pat->all_addr, 1, &e->env->from);
×
1065
      }
1066
      return pat->pat_not ^ result;
×
1067
    }
1068
    case MUTT_PAT_COLLAPSED:
×
1069
      return pat->pat_not ^ (e->collapsed && e->num_hidden > 1);
×
1070
    case MUTT_PAT_CRYPT_SIGN:
1071
      if (!WithCrypto)
1072
      {
1073
        print_crypt_pattern_op_error(pat->op);
1074
        return false;
1075
      }
1076
      return pat->pat_not ^ ((e->security & SEC_SIGN) ? 1 : 0);
×
1077
    case MUTT_PAT_CRYPT_VERIFIED:
1078
      if (!WithCrypto)
1079
      {
1080
        print_crypt_pattern_op_error(pat->op);
1081
        return false;
1082
      }
1083
      return pat->pat_not ^ ((e->security & SEC_GOODSIGN) ? 1 : 0);
×
1084
    case MUTT_PAT_CRYPT_ENCRYPT:
1085
      if (!WithCrypto)
1086
      {
1087
        print_crypt_pattern_op_error(pat->op);
1088
        return false;
1089
      }
1090
      return pat->pat_not ^ ((e->security & SEC_ENCRYPT) ? 1 : 0);
×
1091
    case MUTT_PAT_PGP_KEY:
1092
      if (!(WithCrypto & APPLICATION_PGP))
1093
      {
1094
        print_crypt_pattern_op_error(pat->op);
1095
        return false;
1096
      }
1097
      return pat->pat_not ^ ((e->security & PGP_KEY) == PGP_KEY);
×
1098
    case MUTT_PAT_XLABEL:
×
1099
      if (!e->env)
×
1100
        return false;
1101
      return pat->pat_not ^ (e->env->x_label && patmatch(pat, e->env->x_label));
×
1102
    case MUTT_PAT_DRIVER_TAGS:
×
1103
    {
1104
      return match_tags(pat, &e->tags);
×
1105
    }
1106
    case MUTT_PAT_HORMEL:
×
1107
      if (!e->env)
×
1108
        return false;
1109
      return pat->pat_not ^ (e->env->spam.data && patmatch(pat, e->env->spam.data));
×
1110
    case MUTT_PAT_DUPLICATED:
×
1111
      return pat->pat_not ^ (e->thread && e->thread->duplicate_thread);
×
1112
    case MUTT_PAT_MIMEATTACH:
×
1113
    {
1114
      int count = msg ? mutt_count_body_parts(e, msg->fp) : 0;
×
1115
      return pat->pat_not ^
×
1116
             (count >= pat->min && (pat->max == MUTT_MAXRANGE || count <= pat->max));
×
1117
    }
1118
    case MUTT_PAT_MIMETYPE:
×
1119
      if (!m || !msg)
×
1120
        return false;
1121
      return pat->pat_not ^ match_mime_content_type(pat, e, msg->fp);
×
1122
    case MUTT_PAT_UNREFERENCED:
×
1123
      return pat->pat_not ^ (e->thread && !e->thread->child);
×
1124
    case MUTT_PAT_BROKEN:
×
1125
      return pat->pat_not ^ (e->thread && e->thread->fake_thread);
×
1126
    case MUTT_PAT_NEWSGROUPS:
×
1127
      if (!e->env)
×
1128
        return false;
1129
      return pat->pat_not ^ (e->env->newsgroups && patmatch(pat, e->env->newsgroups));
×
1130
  }
1131
  mutt_error(_("error: unknown op %d (report this error)"), pat->op);
×
1132
  return false;
×
1133
}
1134

1135
/**
1136
 * mutt_pattern_exec - Match a pattern against an email header
1137
 * @param pat   Pattern to match
1138
 * @param flags Flags, e.g. #MUTT_MATCH_FULL_ADDRESS
1139
 * @param m     Mailbox
1140
 * @param e     Email
1141
 * @param cache Cache for common Patterns
1142
 * @retval true Success, pattern matched
1143
 * @retval false Pattern did not match
1144
 *
1145
 * flags: MUTT_MATCH_FULL_ADDRESS: match both personal and machine address
1146
 * cache: For repeated matches against the same Header, passing in non-NULL will
1147
 *        store some of the cacheable pattern matches in this structure.
1148
 */
1149
bool mutt_pattern_exec(struct Pattern *pat, PatternExecFlags flags,
×
1150
                       struct Mailbox *m, struct Email *e, struct PatternCache *cache)
1151
{
1152
  const bool needs_msg = pattern_needs_msg(m, pat);
×
1153
  struct Message *msg = needs_msg ? mx_msg_open(m, e) : NULL;
×
1154
  if (needs_msg && !msg)
×
1155
  {
1156
    return false;
1157
  }
1158
  const bool matched = pattern_exec(pat, flags, m, e, msg, cache);
×
1159
  mx_msg_close(m, &msg);
×
1160
  return matched;
×
1161
}
1162

1163
/**
1164
 * mutt_pattern_alias_exec - Match a pattern against an alias
1165
 * @param pat   Pattern to match
1166
 * @param flags Flags, e.g. #MUTT_MATCH_FULL_ADDRESS
1167
 * @param av    AliasView
1168
 * @param cache Cache for common Patterns
1169
 * @retval true Success, pattern matched
1170
 * @retval false Pattern did not match
1171
 *
1172
 * flags: MUTT_MATCH_FULL_ADDRESS: match both personal and machine address
1173
 * cache: For repeated matches against the same Alias, passing in non-NULL will
1174
 *        store some of the cacheable pattern matches in this structure.
1175
 */
1176
bool mutt_pattern_alias_exec(struct Pattern *pat, PatternExecFlags flags,
×
1177
                             struct AliasView *av, struct PatternCache *cache)
1178
{
1179
  switch (pat->op)
×
1180
  {
1181
    case MUTT_PAT_FROM: /* alias */
×
1182
      if (!av->alias)
×
1183
        return false;
1184
      return pat->pat_not ^ (av->alias->name && patmatch(pat, av->alias->name));
×
1185
    case MUTT_PAT_CC: /* comment */
×
1186
      if (!av->alias)
×
1187
        return false;
1188
      return pat->pat_not ^ (av->alias->comment && patmatch(pat, av->alias->comment));
×
1189
    case MUTT_PAT_TO: /* alias address list */
×
1190
      if (!av->alias)
×
1191
        return false;
1192
      return pat->pat_not ^ match_addrlist(pat, (flags & MUTT_MATCH_FULL_ADDRESS),
×
1193
                                           1, &av->alias->addr);
1194
    case MUTT_PAT_DRIVER_TAGS:
×
1195
      if (!av->alias)
×
1196
        return false;
1197
      return match_tags(pat, &av->alias->tags);
×
1198
    case MUTT_PAT_AND:
×
1199
      return pat->pat_not ^ (perform_alias_and(pat->child, flags, av, cache) > 0);
×
1200
    case MUTT_PAT_OR:
×
1201
      return pat->pat_not ^ (perform_alias_or(pat->child, flags, av, cache) > 0);
×
1202
  }
1203

1204
  return false;
1205
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc