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

neomutt / neomutt / 24648356273

18 Apr 2026 01:24PM UTC coverage: 42.851% (+0.5%) from 42.375%
24648356273

push

github

flatcap
merge: fix security issues

Raised by evilrabbit on Mutt devel mailing list.

 * security: fix GSSAPI buffer underflow on short unwrapped tokens
 * security: reject percent-encoded NUL bytes in URL decoding
 * security: skip CN fallback when SAN dNSName entries exist (RFC6125)
 * security: cap POP3 UIDL responses to prevent OOM from malicious server

3 of 7 new or added lines in 2 files covered. (42.86%)

3465 existing lines in 53 files now uncovered.

12428 of 29003 relevant lines covered (42.85%)

5272.14 hits per line

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

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

27
/**
28
 * @page pattern_pattern Match patterns to emails
29
 *
30
 * Match patterns to emails
31
 */
32

33
#include "config.h"
34
#include <stdbool.h>
35
#include <stddef.h>
36
#include "mutt/lib.h"
37
#include "config/lib.h"
38
#include "email/lib.h"
39
#include "core/lib.h"
40
#include "alias/gui.h" // IWYU pragma: keep
41
#include "alias/lib.h"
42
#include "gui/lib.h"
43
#include "mutt.h"
44
#include "lib.h"
45
#include "editor/lib.h"
46
#include "history/lib.h"
47
#include "imap/lib.h"
48
#include "menu/lib.h"
49
#include "progress/lib.h"
50
#include "mutt_logging.h"
51
#include "mx.h"
52
#include "search_state.h"
53

54
/**
55
 * @defgroup eat_arg_api Parse a pattern API
56
 *
57
 * Prototype for a function to parse a pattern
58
 *
59
 * @param pat   Pattern to store the results in
60
 * @param flags Flags, e.g. #MUTT_PC_PATTERN_DYNAMIC
61
 * @param s     String to parse
62
 * @param err   Buffer for error messages
63
 * @retval true The pattern was read successfully
64
 */
65
typedef bool (*eat_arg_t)(struct Pattern *pat, PatternCompFlags flags,
66
                          struct Buffer *s, struct Buffer *err);
67

68
/**
69
 * quote_simple - Apply simple quoting to a string
70
 * @param str    String to quote
71
 * @param buf    Buffer for the result
72
 */
73
static void quote_simple(const char *str, struct Buffer *buf)
1✔
74
{
75
  buf_reset(buf);
1✔
76
  buf_addch(buf, '"');
1✔
77
  while (*str)
13✔
78
  {
79
    if ((*str == '\\') || (*str == '"'))
12✔
UNCOV
80
      buf_addch(buf, '\\');
×
81
    buf_addch(buf, *str++);
12✔
82
  }
83
  buf_addch(buf, '"');
1✔
84
}
1✔
85

86
/**
87
 * mutt_check_simple - Convert a simple search into a real request
88
 * @param buf    Buffer for the result
89
 * @param simple Search string to convert
90
 */
91
void mutt_check_simple(struct Buffer *buf, const char *simple)
15✔
92
{
93
  bool do_simple = true;
94

95
  for (const char *p = buf_string(buf); p && (p[0] != '\0'); p++)
29✔
96
  {
97
    if ((p[0] == '\\') && (p[1] != '\0'))
26✔
98
    {
UNCOV
99
      p++;
×
100
    }
101
    else if ((p[0] == '~') || (p[0] == '=') || (p[0] == '%'))
26✔
102
    {
103
      do_simple = false;
104
      break;
105
    }
106
  }
107

108
  /* XXX - is mutt_istr_cmp() right here, or should we use locale's
109
   * equivalences?  */
110

111
  if (do_simple) /* yup, so spoof a real request */
15✔
112
  {
113
    /* convert old tokens into the new format */
114
    if (mutt_istr_equal("all", buf_string(buf)) || mutt_str_equal("^", buf_string(buf)) ||
9✔
115
        mutt_str_equal(".", buf_string(buf))) /* ~A is more efficient */
3✔
116
    {
117
      buf_strcpy(buf, "~A");
2✔
118
    }
119
    else if (mutt_istr_equal("del", buf_string(buf)))
1✔
120
    {
UNCOV
121
      buf_strcpy(buf, "~D");
×
122
    }
123
    else if (mutt_istr_equal("flag", buf_string(buf)))
1✔
124
    {
UNCOV
125
      buf_strcpy(buf, "~F");
×
126
    }
127
    else if (mutt_istr_equal("new", buf_string(buf)))
1✔
128
    {
UNCOV
129
      buf_strcpy(buf, "~N");
×
130
    }
131
    else if (mutt_istr_equal("old", buf_string(buf)))
1✔
132
    {
UNCOV
133
      buf_strcpy(buf, "~O");
×
134
    }
135
    else if (mutt_istr_equal("repl", buf_string(buf)))
1✔
136
    {
137
      buf_strcpy(buf, "~Q");
×
138
    }
139
    else if (mutt_istr_equal("read", buf_string(buf)))
1✔
140
    {
141
      buf_strcpy(buf, "~R");
×
142
    }
143
    else if (mutt_istr_equal("tag", buf_string(buf)))
1✔
144
    {
145
      buf_strcpy(buf, "~T");
×
146
    }
147
    else if (mutt_istr_equal("unread", buf_string(buf)))
1✔
148
    {
149
      buf_strcpy(buf, "~U");
×
150
    }
151
    else
152
    {
153
      struct Buffer *tmp = buf_pool_get();
1✔
154
      quote_simple(buf_string(buf), tmp);
1✔
155
      mutt_file_expand_fmt(buf, simple, buf_string(tmp));
2✔
156
      buf_pool_release(&tmp);
1✔
157
    }
158
  }
159
}
15✔
160

161
/**
162
 * mutt_pattern_alias_func - Perform some Pattern matching for Alias
163
 * @param prompt    Prompt to show the user
164
 * @param mdata     Menu data holding Aliases
165
 * @param action    What to do with the results, e.g. #PAA_TAG
166
 * @param menu      Current menu
167
 * @retval  0 Success
168
 * @retval -1 Failure
169
 */
UNCOV
170
int mutt_pattern_alias_func(char *prompt, struct AliasMenuData *mdata,
×
171
                            enum PatternAlias action, struct Menu *menu)
172
{
173
  int rc = -1;
UNCOV
174
  struct Progress *progress = NULL;
×
UNCOV
175
  struct Buffer *buf = buf_pool_get();
×
176

UNCOV
177
  buf_strcpy(buf, mdata->limit);
×
UNCOV
178
  if (prompt)
×
179
  {
UNCOV
180
    if ((mw_get_field(prompt, buf, MUTT_COMP_CLEAR, HC_PATTERN, &CompletePatternOps, NULL) != 0) ||
×
UNCOV
181
        buf_is_empty(buf))
×
182
    {
UNCOV
183
      buf_pool_release(&buf);
×
UNCOV
184
      return -1;
×
185
    }
186
  }
187

UNCOV
188
  mutt_message(_("Compiling search pattern..."));
×
189

190
  /* Compile the pattern string; "~A" means match-all.  If the simple string
191
   * was expanded into a pattern, compile it for evaluation. */
192
  bool match_all = false;
193
  struct PatternList *pat = NULL;
×
194
  char *simple = buf_strdup(buf);
×
UNCOV
195
  if (simple)
×
196
  {
197
    mutt_check_simple(buf, MUTT_ALIAS_SIMPLESEARCH);
×
UNCOV
198
    const char *pbuf = buf->data;
×
199
    while (*pbuf == ' ')
×
200
      pbuf++;
×
UNCOV
201
    match_all = mutt_str_equal(pbuf, "~A");
×
202

UNCOV
203
    struct Buffer *err = buf_pool_get();
×
204
    pat = mutt_pattern_comp(NULL, buf->data, MUTT_PC_FULL_MSG, err);
×
UNCOV
205
    if (!pat)
×
206
    {
UNCOV
207
      mutt_error("%s", buf_string(err));
×
UNCOV
208
      buf_pool_release(&err);
×
209
      goto bail;
×
210
    }
211
    buf_pool_release(&err);
×
212
  }
213
  else
214
  {
215
    match_all = true;
216
  }
217

UNCOV
218
  progress = progress_new(MUTT_PROGRESS_READ, ARRAY_SIZE(&mdata->ava));
×
219
  progress_set_message(progress, _("Executing command on matching messages..."));
×
220

221
  /* Apply the action (tag, untag, or filter visibility) to each alias
222
   * that matches the compiled pattern */
223
  int vcounter = 0;
224
  struct AliasView *avp = NULL;
225
  ARRAY_FOREACH(avp, &mdata->ava)
×
226
  {
227
    progress_update(progress, ARRAY_FOREACH_IDX_avp, -1);
×
228

UNCOV
229
    if (match_all ||
×
UNCOV
230
        mutt_pattern_alias_exec(SLIST_FIRST(pat), MUTT_MATCH_FULL_ADDRESS, avp, NULL))
×
231
    {
UNCOV
232
      switch (action)
×
233
      {
234
        case PAA_TAG:
×
235
          avp->is_tagged = true;
×
UNCOV
236
          break;
×
UNCOV
237
        case PAA_UNTAG:
×
UNCOV
238
          avp->is_tagged = false;
×
UNCOV
239
          break;
×
UNCOV
240
        case PAA_VISIBLE:
×
241
          avp->is_visible = true;
×
UNCOV
242
          vcounter++;
×
243
          break;
×
244
      }
245
    }
246
    else
247
    {
248
      switch (action)
×
249
      {
250
        case PAA_TAG:
251
        case PAA_UNTAG:
252
          // Do nothing
253
          break;
254
        case PAA_VISIBLE:
×
255
          avp->is_visible = false;
×
256
          break;
×
257
      }
258
    }
259
  }
UNCOV
260
  progress_free(&progress);
×
261

UNCOV
262
  FREE(&mdata->limit);
×
UNCOV
263
  if (!match_all)
×
264
  {
UNCOV
265
    mdata->limit = simple;
×
UNCOV
266
    simple = NULL;
×
267
  }
268

UNCOV
269
  if (menu && (action == PAA_VISIBLE))
×
270
  {
271
    menu->max = vcounter;
×
272
    menu_set_index(menu, 0);
×
273
  }
274

UNCOV
275
  mutt_clear_error();
×
276

277
  rc = 0;
278

279
bail:
×
UNCOV
280
  buf_pool_release(&buf);
×
281
  FREE(&simple);
×
282
  mutt_pattern_free(&pat);
×
283

UNCOV
284
  return rc;
×
285
}
286

287
/**
288
 * mutt_pattern_func - Perform some Pattern matching
289
 * @param mv     Mailbox View
290
 * @param op     Operation to perform, e.g. #MUTT_LIMIT
291
 * @param prompt Prompt to show the user
292
 * @retval  0 Success
293
 * @retval -1 Failure
294
 */
295
int mutt_pattern_func(struct MailboxView *mv, int op, char *prompt)
×
296
{
297
  if (!mv || !mv->mailbox)
×
298
    return -1;
299

300
  struct Mailbox *m = mv->mailbox;
301

UNCOV
302
  struct Buffer *err = NULL;
×
303
  int rc = -1;
UNCOV
304
  struct Progress *progress = NULL;
×
UNCOV
305
  struct Buffer *buf = buf_pool_get();
×
306
  bool interrupted = false;
307

UNCOV
308
  buf_strcpy(buf, mv->pattern);
×
UNCOV
309
  if (prompt || (op != MUTT_LIMIT))
×
310
  {
311
    if ((mw_get_field(prompt, buf, MUTT_COMP_CLEAR, HC_PATTERN, &CompletePatternOps, NULL) != 0) ||
×
UNCOV
312
        buf_is_empty(buf))
×
313
    {
UNCOV
314
      buf_pool_release(&buf);
×
UNCOV
315
      return -1;
×
316
    }
317
  }
318

UNCOV
319
  mutt_message(_("Compiling search pattern..."));
×
320

321
  char *simple = buf_strdup(buf);
×
UNCOV
322
  const char *const c_simple_search = cs_subset_string(NeoMutt->sub, "simple_search");
×
UNCOV
323
  mutt_check_simple(buf, NONULL(c_simple_search));
×
324
  const char *pbuf = buf->data;
×
325
  while (*pbuf == ' ')
×
UNCOV
326
    pbuf++;
×
327
  const bool match_all = mutt_str_equal(pbuf, "~A");
×
328

UNCOV
329
  err = buf_pool_get();
×
330
  struct PatternList *pat = mutt_pattern_comp(mv, buf->data, MUTT_PC_FULL_MSG, err);
×
331
  if (!pat)
×
332
  {
UNCOV
333
    mutt_error("%s", buf_string(err));
×
UNCOV
334
    goto bail;
×
335
  }
336

337
  if ((m->type == MUTT_IMAP) && (!imap_search(m, pat)))
×
338
    goto bail;
×
339

340
  progress = progress_new(MUTT_PROGRESS_READ, (op == MUTT_LIMIT) ? m->msg_count : m->vcount);
×
341
  progress_set_message(progress, _("Executing command on matching messages..."));
×
342

343
  if (op == MUTT_LIMIT)
×
344
  {
345
    m->vcount = 0;
×
346
    mv->vsize = 0;
×
347
    mv->collapsed = false;
×
UNCOV
348
    int padding = mx_msg_padding_size(m);
×
349

350
    for (int i = 0; i < m->msg_count; i++)
×
351
    {
UNCOV
352
      struct Email *e = m->emails[i];
×
353
      if (!e)
×
354
        break;
355

356
      if (SigInt)
×
357
      {
358
        interrupted = true;
359
        SigInt = false;
×
UNCOV
360
        break;
×
361
      }
362
      progress_update(progress, i, -1);
×
363
      /* new limit pattern implicitly uncollapses all threads */
364
      e->vnum = -1;
×
UNCOV
365
      e->visible = false;
×
366
      e->limit_visited = true;
×
UNCOV
367
      e->collapsed = false;
×
368
      e->num_hidden = 0;
×
369

UNCOV
370
      if (match_all ||
×
UNCOV
371
          mutt_pattern_exec(SLIST_FIRST(pat), MUTT_MATCH_FULL_ADDRESS, m, e, NULL))
×
372
      {
UNCOV
373
        e->vnum = m->vcount;
×
UNCOV
374
        e->visible = true;
×
375
        m->v2r[m->vcount] = i;
×
376
        m->vcount++;
×
UNCOV
377
        struct Body *b = e->body;
×
378
        mv->vsize += b->length + b->offset - b->hdr_offset + padding;
×
379
      }
380
    }
381
  }
382
  else
383
  {
384
    for (int i = 0; i < m->vcount; i++)
×
385
    {
386
      struct Email *e = mutt_get_virt_email(m, i);
×
387
      if (!e)
×
UNCOV
388
        continue;
×
389

390
      if (SigInt)
×
391
      {
392
        interrupted = true;
393
        SigInt = false;
×
394
        break;
×
395
      }
UNCOV
396
      progress_update(progress, i, -1);
×
UNCOV
397
      if (mutt_pattern_exec(SLIST_FIRST(pat), MUTT_MATCH_FULL_ADDRESS, m, e, NULL))
×
398
      {
UNCOV
399
        switch (op)
×
400
        {
UNCOV
401
          case MUTT_UNDELETE:
×
402
            mutt_set_flag(m, e, MUTT_PURGE, false, true);
×
403
            FALLTHROUGH;
404

UNCOV
405
          case MUTT_DELETE:
×
406
            mutt_set_flag(m, e, MUTT_DELETE, (op == MUTT_DELETE), true);
×
UNCOV
407
            break;
×
UNCOV
408
          case MUTT_TAG:
×
409
          case MUTT_UNTAG:
410
            mutt_set_flag(m, e, MUTT_TAG, (op == MUTT_TAG), true);
×
UNCOV
411
            break;
×
412
        }
413
      }
414
    }
415
  }
UNCOV
416
  progress_free(&progress);
×
417

418
  mutt_clear_error();
×
419

UNCOV
420
  if (op == MUTT_LIMIT)
×
421
  {
422
    /* drop previous limit pattern */
423
    FREE(&mv->pattern);
×
424
    mutt_pattern_free(&mv->limit_pattern);
×
425

426
    if (m->msg_count && !m->vcount)
×
427
      mutt_error(_("No messages matched criteria"));
×
428

429
    /* record new limit pattern, unless match all */
UNCOV
430
    if (!match_all)
×
431
    {
432
      mv->pattern = simple;
×
UNCOV
433
      simple = NULL; /* don't clobber it */
×
434
      mv->limit_pattern = mutt_pattern_comp(mv, buf->data, MUTT_PC_FULL_MSG, err);
×
435
    }
436
  }
437

UNCOV
438
  if (interrupted)
×
439
    mutt_error(_("Search interrupted"));
×
440

441
  rc = 0;
442

443
bail:
×
UNCOV
444
  buf_pool_release(&buf);
×
UNCOV
445
  buf_pool_release(&err);
×
446
  FREE(&simple);
×
UNCOV
447
  mutt_pattern_free(&pat);
×
448

449
  return rc;
×
450
}
451

452
/**
453
 * mutt_search_command - Perform a search
454
 * @param mv    Mailbox view to search through
455
 * @param menu  Current Menu
456
 * @param cur   Index number of current email
457
 * @param state Current search state
458
 * @param flags Search flags, e.g. SEARCH_PROMPT
459
 * @retval >=0  Index of matching email
460
 * @retval -1   No match, or error
461
 */
462
int mutt_search_command(struct MailboxView *mv, struct Menu *menu, int cur,
×
463
                        struct SearchState *state, SearchFlags flags)
464
{
465
  struct Progress *progress = NULL;
×
466
  int rc = -1;
UNCOV
467
  struct Mailbox *m = mv ? mv->mailbox : NULL;
×
UNCOV
468
  if (!m)
×
469
    return -1;
470
  bool pattern_changed = false;
471

UNCOV
472
  if (buf_is_empty(state->string) || (flags & SEARCH_PROMPT))
×
473
  {
UNCOV
474
    if ((mw_get_field((state->reverse) ? _("Reverse search for: ") : _("Search for: "),
×
475
                      state->string, MUTT_COMP_CLEAR, HC_PATTERN,
UNCOV
476
                      &CompletePatternOps, NULL) != 0) ||
×
UNCOV
477
        buf_is_empty(state->string))
×
478
    {
UNCOV
479
      goto done;
×
480
    }
481

482
    /* compare the *expanded* version of the search pattern in case
483
     * $simple_search has changed while we were searching */
484
    struct Buffer *tmp = buf_pool_get();
×
UNCOV
485
    buf_copy(tmp, state->string);
×
UNCOV
486
    const char *const c_simple_search = cs_subset_string(NeoMutt->sub, "simple_search");
×
UNCOV
487
    mutt_check_simple(tmp, NONULL(c_simple_search));
×
488
    if (!buf_str_equal(tmp, state->string_expn))
×
489
    {
490
      mutt_pattern_free(&state->pattern);
×
UNCOV
491
      buf_copy(state->string_expn, tmp);
×
492
      buf_pool_release(&tmp);
×
493
    }
494
  }
495

UNCOV
496
  if (!state->pattern)
×
497
  {
UNCOV
498
    mutt_message(_("Compiling search pattern..."));
×
UNCOV
499
    mutt_pattern_free(&state->pattern);
×
500
    struct Buffer *err = buf_pool_get();
×
501
    state->pattern = mutt_pattern_comp(mv, state->string_expn->data, MUTT_PC_FULL_MSG, err);
×
502
    pattern_changed = true;
503
    if (!state->pattern)
×
504
    {
UNCOV
505
      mutt_error("%s", buf_string(err));
×
506
      buf_free(&err);
×
507
      buf_reset(state->string);
×
508
      buf_reset(state->string_expn);
×
UNCOV
509
      return -1;
×
510
    }
UNCOV
511
    buf_free(&err);
×
512
    mutt_clear_error();
×
513
  }
514

515
  if (pattern_changed)
516
  {
517
    for (int i = 0; i < m->msg_count; i++)
×
UNCOV
518
      m->emails[i]->searched = false;
×
519
    if ((m->type == MUTT_IMAP) && (!imap_search(m, state->pattern)))
×
520
      return -1;
521
  }
522

523
  int incr = state->reverse ? -1 : 1;
×
524
  if (flags & SEARCH_OPPOSITE)
×
525
    incr = -incr;
×
526

527
  progress = progress_new(MUTT_PROGRESS_READ, m->vcount);
×
528
  progress_set_message(progress, _("Searching..."));
×
529

UNCOV
530
  const bool c_wrap_search = cs_subset_bool(NeoMutt->sub, "wrap_search");
×
UNCOV
531
  for (int i = cur + incr, j = 0; j != m->vcount; j++)
×
532
  {
533
    const char *msg = NULL;
534
    progress_update(progress, j, -1);
×
535
    if (i > m->vcount - 1)
×
536
    {
537
      i = 0;
UNCOV
538
      if (c_wrap_search)
×
539
      {
540
        msg = _("Search wrapped to top");
×
541
      }
542
      else
543
      {
544
        mutt_message(_("Search hit bottom without finding match"));
×
UNCOV
545
        goto done;
×
546
      }
547
    }
UNCOV
548
    else if (i < 0)
×
549
    {
550
      i = m->vcount - 1;
×
551
      if (c_wrap_search)
×
552
      {
UNCOV
553
        msg = _("Search wrapped to bottom");
×
554
      }
555
      else
556
      {
UNCOV
557
        mutt_message(_("Search hit top without finding match"));
×
UNCOV
558
        goto done;
×
559
      }
560
    }
561

UNCOV
562
    struct Email *e = mutt_get_virt_email(m, i);
×
UNCOV
563
    if (!e)
×
564
      goto done;
×
565

566
    if (e->searched)
×
567
    {
568
      /* if we've already evaluated this message, use the cached value */
569
      if (e->matched)
×
570
      {
UNCOV
571
        mutt_clear_error();
×
UNCOV
572
        if (msg && *msg)
×
573
          mutt_message("%s", msg);
×
574
        rc = i;
UNCOV
575
        goto done;
×
576
      }
577
    }
578
    else
579
    {
580
      /* remember that we've already searched this message */
UNCOV
581
      e->searched = true;
×
582
      e->matched = mutt_pattern_exec(SLIST_FIRST(state->pattern),
×
583
                                     MUTT_MATCH_FULL_ADDRESS, m, e, NULL);
UNCOV
584
      if (e->matched)
×
585
      {
UNCOV
586
        mutt_clear_error();
×
587
        if (msg && *msg)
×
588
          mutt_message("%s", msg);
×
589
        rc = i;
UNCOV
590
        goto done;
×
591
      }
592
    }
593

UNCOV
594
    if (SigInt)
×
595
    {
UNCOV
596
      mutt_error(_("Search interrupted"));
×
597
      SigInt = false;
×
598
      goto done;
×
599
    }
600

UNCOV
601
    i += incr;
×
602
  }
603

604
  mutt_error(_("Not found"));
×
UNCOV
605
done:
×
606
  progress_free(&progress);
×
UNCOV
607
  return rc;
×
608
}
609

610
/**
611
 * mutt_search_alias_command - Perform a search
612
 * @param menu  Menu to search through
613
 * @param cur   Index number of current email
614
 * @param state Current search state
615
 * @param flags Search flags, e.g. SEARCH_PROMPT
616
 * @retval >=0  Index of matching alias
617
 * @retval -1   No match, or error
618
 */
UNCOV
619
int mutt_search_alias_command(struct Menu *menu, int cur,
×
620
                              struct SearchState *state, SearchFlags flags)
621
{
622
  struct Progress *progress = NULL;
×
623
  const struct AliasMenuData *mdata = menu->mdata;
×
624
  const struct AliasViewArray *ava = &mdata->ava;
625
  int rc = -1;
626
  bool pattern_changed = false;
627

UNCOV
628
  if (buf_is_empty(state->string) || flags & SEARCH_PROMPT)
×
629
  {
UNCOV
630
    if ((mw_get_field(state->reverse ? _("Reverse search for: ") : _("Search for: "),
×
631
                      state->string, MUTT_COMP_CLEAR, HC_PATTERN,
UNCOV
632
                      &CompletePatternOps, NULL) != 0) ||
×
UNCOV
633
        buf_is_empty(state->string))
×
634
    {
635
      goto done;
×
636
    }
637

638
    /* compare the *expanded* version of the search pattern in case
639
     * $simple_search has changed while we were searching */
UNCOV
640
    struct Buffer *tmp = buf_pool_get();
×
UNCOV
641
    buf_copy(tmp, state->string);
×
UNCOV
642
    mutt_check_simple(tmp, MUTT_ALIAS_SIMPLESEARCH);
×
UNCOV
643
    if (!buf_str_equal(tmp, state->string_expn))
×
644
    {
UNCOV
645
      mutt_pattern_free(&state->pattern);
×
646
      buf_copy(state->string_expn, tmp);
×
UNCOV
647
      buf_pool_release(&tmp);
×
648
    }
649
  }
650

651
  if (!state->pattern)
×
652
  {
UNCOV
653
    mutt_message(_("Compiling search pattern..."));
×
UNCOV
654
    struct Buffer *err = buf_pool_get();
×
UNCOV
655
    state->pattern = mutt_pattern_comp(NULL, state->string_expn->data, MUTT_PC_FULL_MSG, err);
×
656
    pattern_changed = true;
657
    if (!state->pattern)
×
658
    {
659
      mutt_error("%s", buf_string(err));
×
UNCOV
660
      buf_free(&err);
×
661
      buf_reset(state->string);
×
662
      buf_reset(state->string_expn);
×
663
      return -1;
×
664
    }
UNCOV
665
    buf_free(&err);
×
UNCOV
666
    mutt_clear_error();
×
667
  }
668

669
  if (pattern_changed)
670
  {
671
    struct AliasView *av = NULL;
UNCOV
672
    ARRAY_FOREACH(av, ava)
×
673
    {
UNCOV
674
      av->is_searched = false;
×
675
    }
676
  }
677

678
  int incr = state->reverse ? -1 : 1;
×
679
  if (flags & SEARCH_OPPOSITE)
×
UNCOV
680
    incr = -incr;
×
681

682
  progress = progress_new(MUTT_PROGRESS_READ, ARRAY_SIZE(ava));
×
UNCOV
683
  progress_set_message(progress, _("Searching..."));
×
684

UNCOV
685
  const bool c_wrap_search = cs_subset_bool(NeoMutt->sub, "wrap_search");
×
UNCOV
686
  for (int i = cur + incr, j = 0; j != ARRAY_SIZE(ava); j++)
×
687
  {
688
    const char *msg = NULL;
UNCOV
689
    progress_update(progress, j, -1);
×
690
    if (i > ARRAY_SIZE(ava) - 1)
×
691
    {
692
      i = 0;
UNCOV
693
      if (c_wrap_search)
×
694
      {
695
        msg = _("Search wrapped to top");
×
696
      }
697
      else
698
      {
699
        mutt_message(_("Search hit bottom without finding match"));
×
UNCOV
700
        goto done;
×
701
      }
702
    }
UNCOV
703
    else if (i < 0)
×
704
    {
705
      i = ARRAY_SIZE(ava) - 1;
×
706
      if (c_wrap_search)
×
707
      {
UNCOV
708
        msg = _("Search wrapped to bottom");
×
709
      }
710
      else
711
      {
UNCOV
712
        mutt_message(_("Search hit top without finding match"));
×
UNCOV
713
        goto done;
×
714
      }
715
    }
716

UNCOV
717
    struct AliasView *av = ARRAY_GET(ava, i);
×
UNCOV
718
    if (av->is_searched)
×
719
    {
720
      /* if we've already evaluated this message, use the cached value */
721
      if (av->is_matched)
×
722
      {
UNCOV
723
        mutt_clear_error();
×
724
        if (msg && *msg)
×
UNCOV
725
          mutt_message("%s", msg);
×
726
        rc = i;
UNCOV
727
        goto done;
×
728
      }
729
    }
730
    else
731
    {
732
      /* remember that we've already searched this message */
733
      av->is_searched = true;
×
734
      av->is_matched = mutt_pattern_alias_exec(SLIST_FIRST(state->pattern),
×
735
                                               MUTT_MATCH_FULL_ADDRESS, av, NULL);
UNCOV
736
      if (av->is_matched)
×
737
      {
UNCOV
738
        mutt_clear_error();
×
739
        if (msg && *msg)
×
740
          mutt_message("%s", msg);
×
741
        rc = i;
UNCOV
742
        goto done;
×
743
      }
744
    }
745

UNCOV
746
    if (SigInt)
×
747
    {
UNCOV
748
      mutt_error(_("Search interrupted"));
×
749
      SigInt = false;
×
750
      goto done;
×
751
    }
752

UNCOV
753
    i += incr;
×
754
  }
755

756
  mutt_error(_("Not found"));
×
UNCOV
757
done:
×
758
  progress_free(&progress);
×
UNCOV
759
  return rc;
×
760
}
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