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

neomutt / neomutt / 22837812302

07 Mar 2026 01:58PM UTC coverage: 42.377% (+0.009%) from 42.368%
22837812302

push

github

flatcap
rename MT_COLOR_SIDEBAR_SPOOL_FILE

Rename MT_COLOR_SIDEBAR_SPOOLFILE to MT_COLOR_SIDEBAR_SPOOL_FILE
to match the colour name.

0 of 2 new or added lines in 1 file covered. (0.0%)

2351 existing lines in 28 files now uncovered.

12119 of 28598 relevant lines covered (42.38%)

2821.95 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 "mx.h"
55
#ifndef USE_FMEMOPEN
56
#include <sys/stat.h>
57
#endif
58

59
static bool pattern_exec(struct Pattern *pat, PatternExecFlags flags,
60
                         struct Mailbox *m, struct Email *e,
61
                         struct Message *msg, struct PatternCache *cache);
62

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

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

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

115
  bool match = false;
116

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

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

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

156
    if (needs_body)
×
157
    {
158
      mutt_parse_mime_message(e, msg->fp);
×
159

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

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

184
#ifdef USE_FMEMOPEN
185
    mutt_file_fclose(&state.fp_out);
186
    len = tempsize;
187

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

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

282
  if (c_thorough_search)
×
283
    mutt_file_fclose(&fp);
×
284

285
#ifdef USE_FMEMOPEN
286
  FREE(&temp);
287
#endif
288

289
  return match;
290
}
291

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

600
  return mutt_pattern_exec(SLIST_FIRST(pat), flags, m, t->parent->message, NULL);
×
601
}
602

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

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

623
  return 0;
624
}
625

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

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

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

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

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

674
  bool rc = eval_date_minmax(pat, pat->p.str, err);
×
675
  buf_pool_release(&err);
×
676

677
  return rc;
×
678
}
679

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

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

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

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

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

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

755
    FREE(&buf);
×
756
    mutt_file_fclose(&fp);
×
757
    unlink(buf_string(tempfile));
×
758
    buf_pool_release(&tempfile);
×
759

760
    if (match)
×
761
      return match;
762
  }
763

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

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

782
    FREE(&buf);
×
783
    mutt_file_fclose(&fp);
×
784
  }
785

786
  return match;
×
787
}
788

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

803
  if ((pat->op == MUTT_PAT_MIMETYPE) || (pat->op == MUTT_PAT_MIMEATTACH))
×
804
  {
805
    return true;
806
  }
807

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

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

825
  return false;
826
}
827

828
/**
829
 * pattern_exec - Match a pattern against an email header
830
 * @param pat   Pattern to match
831
 * @param flags Flags, e.g. #MUTT_MATCH_FULL_ADDRESS
832
 * @param m     Mailbox
833
 * @param e     Email
834
 * @param msg   MEssage
835
 * @param cache Cache for common Patterns
836
 * @retval true Success, pattern matched
837
 * @retval false Pattern did not match
838
 *
839
 * flags: MUTT_MATCH_FULL_ADDRESS: match both personal and machine address
840
 * cache: For repeated matches against the same Header, passing in non-NULL will
841
 *        store some of the cacheable pattern matches in this structure.
842
 */
843
static bool pattern_exec(struct Pattern *pat, PatternExecFlags flags,
×
844
                         struct Mailbox *m, struct Email *e,
845
                         struct Message *msg, struct PatternCache *cache)
846
{
847
  /* Dispatch on the pattern operation type.  Each case tests a specific
848
   * email attribute (flags, dates, addresses, etc.) against the pattern,
849
   * applying pat_not to invert the result when '!' was used. */
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)
×
UNCOV
894
        match_update_dynamic_date(pat);
×
UNCOV
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:
UNCOV
899
      if (pat->sendmode)
×
900
      {
UNCOV
901
        if (!e->body || !e->body->filename)
×
902
          return false;
UNCOV
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;
×
UNCOV
913
      return pat->pat_not ^ msg_search(pat, e, msg);
×
914
    case MUTT_PAT_SERVERSEARCH:
×
UNCOV
915
      if (!m)
×
916
        return false;
UNCOV
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"));
×
UNCOV
922
      return false;
×
923
    case MUTT_PAT_SENDER:
×
UNCOV
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 ^
×
UNCOV
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 ^
×
UNCOV
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 ^
×
UNCOV
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 ^
×
UNCOV
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));
×
UNCOV
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 &&
×
UNCOV
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) ||
×
UNCOV
967
                             match_reference(pat, &e->env->in_reply_to));
×
968
    case MUTT_PAT_ADDRESS:
×
UNCOV
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:
×
UNCOV
975
      if (!e->env)
×
976
        return false;
UNCOV
977
      return pat->pat_not ^ match_addrlist(pat, (flags & MUTT_MATCH_FULL_ADDRESS), 3,
×
978
                                           &e->env->to, &e->env->cc, &e->env->bcc);
UNCOV
979
    case MUTT_PAT_LIST: /* known list, subscribed or not */
×
980
    {
UNCOV
981
      if (!e->env)
×
982
        return false;
983

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

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

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

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

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

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

1206
  return false;
1207
}
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