Coverage Report

Created: 2023-09-25 07:17

/src/neomutt/pattern/compile.c
Line
Count
Source (jump to first uncovered line)
1
/**
2
 * @file
3
 * Compile a Pattern
4
 *
5
 * @authors
6
 * Copyright (C) 2019 Pietro Cerutti <gahr@gahr.ch>
7
 * Copyright (C) 2020 Richard Russon <rich@flatcap.org>
8
 * Copyright (C) 2020 R Primus <rprimus@gmail.com>
9
 *
10
 * @copyright
11
 * This program is free software: you can redistribute it and/or modify it under
12
 * the terms of the GNU General Public License as published by the Free Software
13
 * Foundation, either version 2 of the License, or (at your option) any later
14
 * version.
15
 *
16
 * This program is distributed in the hope that it will be useful, but WITHOUT
17
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
18
 * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
19
 * details.
20
 *
21
 * You should have received a copy of the GNU General Public License along with
22
 * this program.  If not, see <http://www.gnu.org/licenses/>.
23
 */
24
25
/**
26
 * @page pattern_compile Compile a Pattern
27
 *
28
 * Compile a Pattern
29
 */
30
31
#include "config.h"
32
#include <ctype.h>
33
#include <stdbool.h>
34
#include <stdint.h>
35
#include <stdio.h>
36
#include <stdlib.h>
37
#include <string.h>
38
#include <time.h>
39
#include "private.h"
40
#include "mutt/lib.h"
41
#include "address/lib.h"
42
#include "config/lib.h"
43
#include "email/lib.h" // IWYU pragma: keep
44
#include "core/lib.h"
45
#include "lib.h"
46
#include "parse/lib.h"
47
#include "globals.h" // IWYU pragma: keep
48
#include "mview.h"
49
50
struct Menu;
51
52
// clang-format off
53
typedef uint16_t ParseDateRangeFlags; ///< Flags for parse_date_range(), e.g. #MUTT_PDR_MINUS
54
0
#define MUTT_PDR_NO_FLAGS       0  ///< No flags are set
55
0
#define MUTT_PDR_MINUS    (1 << 0) ///< Pattern contains a range
56
0
#define MUTT_PDR_PLUS     (1 << 1) ///< Extend the range using '+'
57
0
#define MUTT_PDR_WINDOW   (1 << 2) ///< Extend the range in both directions using '*'
58
0
#define MUTT_PDR_ABSOLUTE (1 << 3) ///< Absolute pattern range
59
0
#define MUTT_PDR_DONE     (1 << 4) ///< Pattern parse successfully
60
0
#define MUTT_PDR_ERROR    (1 << 8) ///< Invalid pattern
61
// clang-format on
62
63
0
#define MUTT_PDR_ERRORDONE (MUTT_PDR_ERROR | MUTT_PDR_DONE)
64
65
/**
66
 * eat_regex - Parse a regex - Implements ::eat_arg_t - @ingroup eat_arg_api
67
 */
68
static bool eat_regex(struct Pattern *pat, PatternCompFlags flags,
69
                      struct Buffer *s, struct Buffer *err)
70
0
{
71
0
  struct Buffer *buf = buf_pool_get();
72
0
  bool rc = false;
73
0
  char *pexpr = s->dptr;
74
0
  if ((parse_extract_token(buf, s, TOKEN_PATTERN | TOKEN_COMMENT) != 0) || !buf->data)
75
0
  {
76
0
    buf_printf(err, _("Error in expression: %s"), pexpr);
77
0
    goto out;
78
0
  }
79
0
  if (buf_is_empty(buf))
80
0
  {
81
0
    buf_addstr(err, _("Empty expression"));
82
0
    goto out;
83
0
  }
84
85
0
  if (pat->string_match)
86
0
  {
87
0
    pat->p.str = mutt_str_dup(buf->data);
88
0
    pat->ign_case = mutt_mb_is_lower(buf->data);
89
0
  }
90
0
  else if (pat->group_match)
91
0
  {
92
0
    pat->p.group = mutt_pattern_group(buf->data);
93
0
  }
94
0
  else
95
0
  {
96
0
    pat->p.regex = mutt_mem_calloc(1, sizeof(regex_t));
97
#ifdef USE_DEBUG_GRAPHVIZ
98
    pat->raw_pattern = mutt_str_dup(buf->data);
99
#endif
100
0
    uint16_t case_flags = mutt_mb_is_lower(buf->data) ? REG_ICASE : 0;
101
0
    int rc2 = REG_COMP(pat->p.regex, buf->data, REG_NEWLINE | REG_NOSUB | case_flags);
102
0
    if (rc2 != 0)
103
0
    {
104
0
      char errmsg[256] = { 0 };
105
0
      regerror(rc2, pat->p.regex, errmsg, sizeof(errmsg));
106
0
      buf_printf(err, "'%s': %s", buf->data, errmsg);
107
0
      FREE(&pat->p.regex);
108
0
      goto out;
109
0
    }
110
0
  }
111
112
0
  rc = true;
113
114
0
out:
115
0
  buf_pool_release(&buf);
116
0
  return rc;
117
0
}
118
119
/**
120
 * add_query_msgid - Parse a Message-Id and add it to a list - Implements ::mutt_file_map_t - @ingroup mutt_file_map_api
121
 * @retval true Always
122
 */
123
static bool add_query_msgid(char *line, int line_num, void *user_data)
124
0
{
125
0
  struct ListHead *msgid_list = (struct ListHead *) (user_data);
126
0
  char *nows = mutt_str_skip_whitespace(line);
127
0
  if (*nows == '\0')
128
0
    return true;
129
0
  mutt_str_remove_trailing_ws(nows);
130
0
  mutt_list_insert_tail(msgid_list, mutt_str_dup(nows));
131
0
  return true;
132
0
}
133
134
/**
135
 * eat_query - Parse a query for an external search program - Implements ::eat_arg_t - @ingroup eat_arg_api
136
 * @param pat   Pattern to store the results in
137
 * @param flags Flags, e.g. #MUTT_PC_PATTERN_DYNAMIC
138
 * @param s     String to parse
139
 * @param err   Buffer for error messages
140
 * @param m     Mailbox
141
 * @retval true The pattern was read successfully
142
 */
143
static bool eat_query(struct Pattern *pat, PatternCompFlags flags,
144
                      struct Buffer *s, struct Buffer *err, struct Mailbox *m)
145
0
{
146
0
  struct Buffer *cmd_buf = buf_pool_get();
147
0
  struct Buffer *tok_buf = buf_pool_get();
148
0
  bool rc = false;
149
150
0
  FILE *fp = NULL;
151
152
0
  const char *const c_external_search_command = cs_subset_string(NeoMutt->sub, "external_search_command");
153
0
  if (!c_external_search_command)
154
0
  {
155
0
    buf_addstr(err, _("No search command defined"));
156
0
    goto out;
157
0
  }
158
159
0
  char *pexpr = s->dptr;
160
0
  if ((parse_extract_token(tok_buf, s, TOKEN_PATTERN | TOKEN_COMMENT) != 0) ||
161
0
      !tok_buf->data)
162
0
  {
163
0
    buf_printf(err, _("Error in expression: %s"), pexpr);
164
0
    goto out;
165
0
  }
166
0
  if (*tok_buf->data == '\0')
167
0
  {
168
0
    buf_addstr(err, _("Empty expression"));
169
0
    goto out;
170
0
  }
171
172
0
  buf_addstr(cmd_buf, c_external_search_command);
173
0
  buf_addch(cmd_buf, ' ');
174
175
0
  if (m)
176
0
  {
177
0
    char *escaped_folder = mutt_path_escape(mailbox_path(m));
178
0
    mutt_debug(LL_DEBUG2, "escaped folder path: %s\n", escaped_folder);
179
0
    buf_addch(cmd_buf, '\'');
180
0
    buf_addstr(cmd_buf, escaped_folder);
181
0
    buf_addch(cmd_buf, '\'');
182
0
  }
183
0
  else
184
0
  {
185
0
    buf_addch(cmd_buf, '/');
186
0
  }
187
0
  buf_addch(cmd_buf, ' ');
188
0
  buf_addstr(cmd_buf, tok_buf->data);
189
190
0
  mutt_message(_("Running search command: %s ..."), cmd_buf->data);
191
0
  pat->is_multi = true;
192
0
  mutt_list_clear(&pat->p.multi_cases);
193
0
  pid_t pid = filter_create(cmd_buf->data, NULL, &fp, NULL, EnvList);
194
0
  if (pid < 0)
195
0
  {
196
0
    buf_printf(err, "unable to fork command: %s\n", cmd_buf->data);
197
0
    goto out;
198
0
  }
199
200
0
  mutt_file_map_lines(add_query_msgid, &pat->p.multi_cases, fp, MUTT_RL_NO_FLAGS);
201
0
  mutt_file_fclose(&fp);
202
0
  filter_wait(pid);
203
204
0
  rc = true;
205
206
0
out:
207
0
  buf_pool_release(&cmd_buf);
208
0
  buf_pool_release(&tok_buf);
209
0
  return rc;
210
0
}
211
212
/**
213
 * get_offset - Calculate a symbolic offset
214
 * @param tm   Store the time here
215
 * @param s    string to parse
216
 * @param sign Sign of range, 1 for positive, -1 for negative
217
 * @retval ptr Next char after parsed offset
218
 *
219
 * - Ny years
220
 * - Nm months
221
 * - Nw weeks
222
 * - Nd days
223
 */
224
static const char *get_offset(struct tm *tm, const char *s, int sign)
225
0
{
226
0
  char *ps = NULL;
227
0
  int offset = strtol(s, &ps, 0);
228
0
  if (((sign < 0) && (offset > 0)) || ((sign > 0) && (offset < 0)))
229
0
    offset = -offset;
230
231
0
  switch (*ps)
232
0
  {
233
0
    case 'y':
234
0
      tm->tm_year += offset;
235
0
      break;
236
0
    case 'm':
237
0
      tm->tm_mon += offset;
238
0
      break;
239
0
    case 'w':
240
0
      tm->tm_mday += 7 * offset;
241
0
      break;
242
0
    case 'd':
243
0
      tm->tm_mday += offset;
244
0
      break;
245
0
    case 'H':
246
0
      tm->tm_hour += offset;
247
0
      break;
248
0
    case 'M':
249
0
      tm->tm_min += offset;
250
0
      break;
251
0
    case 'S':
252
0
      tm->tm_sec += offset;
253
0
      break;
254
0
    default:
255
0
      return s;
256
0
  }
257
0
  mutt_date_normalize_time(tm);
258
0
  return ps + 1;
259
0
}
260
261
/**
262
 * get_date - Parse a (partial) date in dd/mm/yyyy format
263
 * @param s   String to parse
264
 * @param t   Store the time here
265
 * @param err Buffer for error messages
266
 * @retval ptr First character after the date
267
 *
268
 * This function parses a (partial) date separated by '/'.  The month and year
269
 * are optional and if the year is less than 70 it's assumed to be after 2000.
270
 *
271
 * Examples:
272
 * - "10"         = 10 of this month, this year
273
 * - "10/12"      = 10 of December,   this year
274
 * - "10/12/04"   = 10 of December,   2004
275
 * - "10/12/2008" = 10 of December,   2008
276
 * - "20081210"   = 10 of December,   2008
277
 */
278
static const char *get_date(const char *s, struct tm *t, struct Buffer *err)
279
0
{
280
0
  char *p = NULL;
281
0
  struct tm tm = mutt_date_localtime(mutt_date_now());
282
0
  bool iso8601 = true;
283
284
0
  for (int v = 0; v < 8; v++)
285
0
  {
286
0
    if (s[v] && (s[v] >= '0') && (s[v] <= '9'))
287
0
      continue;
288
289
0
    iso8601 = false;
290
0
    break;
291
0
  }
292
293
0
  if (iso8601)
294
0
  {
295
0
    int year = 0;
296
0
    int month = 0;
297
0
    int mday = 0;
298
0
    sscanf(s, "%4d%2d%2d", &year, &month, &mday);
299
300
0
    t->tm_year = year;
301
0
    if (t->tm_year > 1900)
302
0
      t->tm_year -= 1900;
303
0
    t->tm_mon = month - 1;
304
0
    t->tm_mday = mday;
305
306
0
    if ((t->tm_mday < 1) || (t->tm_mday > 31))
307
0
    {
308
0
      buf_printf(err, _("Invalid day of month: %s"), s);
309
0
      return NULL;
310
0
    }
311
0
    if ((t->tm_mon < 0) || (t->tm_mon > 11))
312
0
    {
313
0
      buf_printf(err, _("Invalid month: %s"), s);
314
0
      return NULL;
315
0
    }
316
317
0
    return (s + 8);
318
0
  }
319
320
0
  t->tm_mday = strtol(s, &p, 10);
321
0
  if ((t->tm_mday < 1) || (t->tm_mday > 31))
322
0
  {
323
0
    buf_printf(err, _("Invalid day of month: %s"), s);
324
0
    return NULL;
325
0
  }
326
0
  if (*p != '/')
327
0
  {
328
    /* fill in today's month and year */
329
0
    t->tm_mon = tm.tm_mon;
330
0
    t->tm_year = tm.tm_year;
331
0
    return p;
332
0
  }
333
0
  p++;
334
0
  t->tm_mon = strtol(p, &p, 10) - 1;
335
0
  if ((t->tm_mon < 0) || (t->tm_mon > 11))
336
0
  {
337
0
    buf_printf(err, _("Invalid month: %s"), p);
338
0
    return NULL;
339
0
  }
340
0
  if (*p != '/')
341
0
  {
342
0
    t->tm_year = tm.tm_year;
343
0
    return p;
344
0
  }
345
0
  p++;
346
0
  t->tm_year = strtol(p, &p, 10);
347
0
  if (t->tm_year < 70) /* year 2000+ */
348
0
    t->tm_year += 100;
349
0
  else if (t->tm_year > 1900)
350
0
    t->tm_year -= 1900;
351
0
  return p;
352
0
}
353
354
/**
355
 * parse_date_range - Parse a date range
356
 * @param pc       String to parse
357
 * @param min      Earlier date
358
 * @param max      Later date
359
 * @param have_min Do we have a base minimum date?
360
 * @param base_min Base minimum date
361
 * @param err      Buffer for error messages
362
 * @retval ptr First character after the date
363
 */
364
static const char *parse_date_range(const char *pc, struct tm *min, struct tm *max,
365
                                    bool have_min, struct tm *base_min, struct Buffer *err)
366
0
{
367
0
  ParseDateRangeFlags flags = MUTT_PDR_NO_FLAGS;
368
0
  while (*pc && ((flags & MUTT_PDR_DONE) == 0))
369
0
  {
370
0
    const char *pt = NULL;
371
0
    char ch = *pc++;
372
0
    SKIPWS(pc);
373
0
    switch (ch)
374
0
    {
375
0
      case '-':
376
0
      {
377
        /* try a range of absolute date minus offset of Ndwmy */
378
0
        pt = get_offset(min, pc, -1);
379
0
        if (pc == pt)
380
0
        {
381
0
          if (flags == MUTT_PDR_NO_FLAGS)
382
0
          { /* nothing yet and no offset parsed => absolute date? */
383
0
            if (!get_date(pc, max, err))
384
0
            {
385
0
              flags |= (MUTT_PDR_ABSOLUTE | MUTT_PDR_ERRORDONE); /* done bad */
386
0
            }
387
0
            else
388
0
            {
389
              /* reestablish initial base minimum if not specified */
390
0
              if (!have_min)
391
0
                memcpy(min, base_min, sizeof(struct tm));
392
0
              flags |= (MUTT_PDR_ABSOLUTE | MUTT_PDR_DONE); /* done good */
393
0
            }
394
0
          }
395
0
          else
396
0
          {
397
0
            flags |= MUTT_PDR_ERRORDONE;
398
0
          }
399
0
        }
400
0
        else
401
0
        {
402
0
          pc = pt;
403
0
          if ((flags == MUTT_PDR_NO_FLAGS) && !have_min)
404
0
          { /* the very first "-3d" without a previous absolute date */
405
0
            max->tm_year = min->tm_year;
406
0
            max->tm_mon = min->tm_mon;
407
0
            max->tm_mday = min->tm_mday;
408
0
          }
409
0
          flags |= MUTT_PDR_MINUS;
410
0
        }
411
0
        break;
412
0
      }
413
0
      case '+':
414
0
      { /* enlarge plus range */
415
0
        pt = get_offset(max, pc, 1);
416
0
        if (pc == pt)
417
0
        {
418
0
          flags |= MUTT_PDR_ERRORDONE;
419
0
        }
420
0
        else
421
0
        {
422
0
          pc = pt;
423
0
          flags |= MUTT_PDR_PLUS;
424
0
        }
425
0
        break;
426
0
      }
427
0
      case '*':
428
0
      { /* enlarge window in both directions */
429
0
        pt = get_offset(min, pc, -1);
430
0
        if (pc == pt)
431
0
        {
432
0
          flags |= MUTT_PDR_ERRORDONE;
433
0
        }
434
0
        else
435
0
        {
436
0
          pc = get_offset(max, pc, 1);
437
0
          flags |= MUTT_PDR_WINDOW;
438
0
        }
439
0
        break;
440
0
      }
441
0
      default:
442
0
        flags |= MUTT_PDR_ERRORDONE;
443
0
    }
444
0
    SKIPWS(pc);
445
0
  }
446
0
  if ((flags & MUTT_PDR_ERROR) && !(flags & MUTT_PDR_ABSOLUTE))
447
0
  { /* get_date has its own error message, don't overwrite it here */
448
0
    buf_printf(err, _("Invalid relative date: %s"), pc - 1);
449
0
  }
450
0
  return (flags & MUTT_PDR_ERROR) ? NULL : pc;
451
0
}
452
453
/**
454
 * adjust_date_range - Put a date range in the correct order
455
 * @param[in,out] min Earlier date
456
 * @param[in,out] max Later date
457
 */
458
static void adjust_date_range(struct tm *min, struct tm *max)
459
0
{
460
0
  if ((min->tm_year > max->tm_year) ||
461
0
      ((min->tm_year == max->tm_year) && (min->tm_mon > max->tm_mon)) ||
462
0
      ((min->tm_year == max->tm_year) && (min->tm_mon == max->tm_mon) &&
463
0
       (min->tm_mday > max->tm_mday)))
464
0
  {
465
0
    int tmp;
466
467
0
    tmp = min->tm_year;
468
0
    min->tm_year = max->tm_year;
469
0
    max->tm_year = tmp;
470
471
0
    tmp = min->tm_mon;
472
0
    min->tm_mon = max->tm_mon;
473
0
    max->tm_mon = tmp;
474
475
0
    tmp = min->tm_mday;
476
0
    min->tm_mday = max->tm_mday;
477
0
    max->tm_mday = tmp;
478
479
0
    min->tm_hour = 0;
480
0
    min->tm_min = 0;
481
0
    min->tm_sec = 0;
482
0
    max->tm_hour = 23;
483
0
    max->tm_min = 59;
484
0
    max->tm_sec = 59;
485
0
  }
486
0
}
487
488
/**
489
 * eval_date_minmax - Evaluate a date-range pattern against 'now'
490
 * @param pat Pattern to modify
491
 * @param s   Pattern string to use
492
 * @param err Buffer for error messages
493
 * @retval true  Pattern valid and updated
494
 * @retval false Pattern invalid
495
 */
496
bool eval_date_minmax(struct Pattern *pat, const char *s, struct Buffer *err)
497
0
{
498
  /* the '0' time is Jan 1, 1970 UTC, so in order to prevent a negative time
499
   * when doing timezone conversion, we use Jan 2, 1970 UTC as the base here */
500
0
  struct tm min = { 0 };
501
0
  min.tm_mday = 2;
502
0
  min.tm_year = 70;
503
504
  /* Arbitrary year in the future.  Don't set this too high or
505
   * mutt_date_make_time() returns something larger than will fit in a time_t
506
   * on some systems */
507
0
  struct tm max = { 0 };
508
0
  max.tm_year = 130;
509
0
  max.tm_mon = 11;
510
0
  max.tm_mday = 31;
511
0
  max.tm_hour = 23;
512
0
  max.tm_min = 59;
513
0
  max.tm_sec = 59;
514
515
0
  if (strchr("<>=", s[0]))
516
0
  {
517
    /* offset from current time
518
     *  <3d  less than three days ago
519
     *  >3d  more than three days ago
520
     *  =3d  exactly three days ago */
521
0
    struct tm *tm = NULL;
522
0
    bool exact = false;
523
524
0
    if (s[0] == '<')
525
0
    {
526
0
      min = mutt_date_localtime(mutt_date_now());
527
0
      tm = &min;
528
0
    }
529
0
    else
530
0
    {
531
0
      max = mutt_date_localtime(mutt_date_now());
532
0
      tm = &max;
533
534
0
      if (s[0] == '=')
535
0
        exact = true;
536
0
    }
537
538
    /* Reset the HMS unless we are relative matching using one of those
539
     * offsets. */
540
0
    char *offset_type = NULL;
541
0
    strtol(s + 1, &offset_type, 0);
542
0
    if (!(*offset_type && strchr("HMS", *offset_type)))
543
0
    {
544
0
      tm->tm_hour = 23;
545
0
      tm->tm_min = 59;
546
0
      tm->tm_sec = 59;
547
0
    }
548
549
    /* force negative offset */
550
0
    get_offset(tm, s + 1, -1);
551
552
0
    if (exact)
553
0
    {
554
      /* start at the beginning of the day in question */
555
0
      memcpy(&min, &max, sizeof(max));
556
0
      min.tm_hour = 0;
557
0
      min.tm_sec = 0;
558
0
      min.tm_min = 0;
559
0
    }
560
0
  }
561
0
  else
562
0
  {
563
0
    const char *pc = s;
564
565
0
    bool have_min = false;
566
0
    bool until_now = false;
567
0
    if (isdigit((unsigned char) *pc))
568
0
    {
569
      /* minimum date specified */
570
0
      pc = get_date(pc, &min, err);
571
0
      if (!pc)
572
0
      {
573
0
        return false;
574
0
      }
575
0
      have_min = true;
576
0
      SKIPWS(pc);
577
0
      if (*pc == '-')
578
0
      {
579
0
        const char *pt = pc + 1;
580
0
        SKIPWS(pt);
581
0
        until_now = (*pt == '\0');
582
0
      }
583
0
    }
584
585
0
    if (!until_now)
586
0
    { /* max date or relative range/window */
587
588
0
      struct tm base_min = { 0 };
589
590
0
      if (!have_min)
591
0
      { /* save base minimum and set current date, e.g. for "-3d+1d" */
592
0
        memcpy(&base_min, &min, sizeof(base_min));
593
0
        min = mutt_date_localtime(mutt_date_now());
594
0
        min.tm_hour = 0;
595
0
        min.tm_sec = 0;
596
0
        min.tm_min = 0;
597
0
      }
598
599
      /* preset max date for relative offsets,
600
       * if nothing follows we search for messages on a specific day */
601
0
      max.tm_year = min.tm_year;
602
0
      max.tm_mon = min.tm_mon;
603
0
      max.tm_mday = min.tm_mday;
604
605
0
      if (!parse_date_range(pc, &min, &max, have_min, &base_min, err))
606
0
      { /* bail out on any parsing error */
607
0
        return false;
608
0
      }
609
0
    }
610
0
  }
611
612
  /* Since we allow two dates to be specified we'll have to adjust that. */
613
0
  adjust_date_range(&min, &max);
614
615
0
  pat->min = mutt_date_make_time(&min, true);
616
0
  pat->max = mutt_date_make_time(&max, true);
617
618
0
  return true;
619
0
}
620
621
/**
622
 * eat_range - Parse a number range - Implements ::eat_arg_t - @ingroup eat_arg_api
623
 */
624
static bool eat_range(struct Pattern *pat, PatternCompFlags flags,
625
                      struct Buffer *s, struct Buffer *err)
626
0
{
627
0
  char *tmp = NULL;
628
0
  bool do_exclusive = false;
629
0
  bool skip_quote = false;
630
631
  /* If simple_search is set to "~m %s", the range will have double quotes
632
   * around it...  */
633
0
  if (*s->dptr == '"')
634
0
  {
635
0
    s->dptr++;
636
0
    skip_quote = true;
637
0
  }
638
0
  if (*s->dptr == '<')
639
0
    do_exclusive = true;
640
0
  if ((*s->dptr != '-') && (*s->dptr != '<'))
641
0
  {
642
    /* range minimum */
643
0
    if (*s->dptr == '>')
644
0
    {
645
0
      pat->max = MUTT_MAXRANGE;
646
0
      pat->min = strtol(s->dptr + 1, &tmp, 0) + 1; /* exclusive range */
647
0
    }
648
0
    else
649
0
    {
650
0
      pat->min = strtol(s->dptr, &tmp, 0);
651
0
    }
652
0
    if (toupper((unsigned char) *tmp) == 'K') /* is there a prefix? */
653
0
    {
654
0
      pat->min *= 1024;
655
0
      tmp++;
656
0
    }
657
0
    else if (toupper((unsigned char) *tmp) == 'M')
658
0
    {
659
0
      pat->min *= 1048576;
660
0
      tmp++;
661
0
    }
662
0
    if (*s->dptr == '>')
663
0
    {
664
0
      s->dptr = tmp;
665
0
      return true;
666
0
    }
667
0
    if (*tmp != '-')
668
0
    {
669
      /* exact value */
670
0
      pat->max = pat->min;
671
0
      s->dptr = tmp;
672
0
      return true;
673
0
    }
674
0
    tmp++;
675
0
  }
676
0
  else
677
0
  {
678
0
    s->dptr++;
679
0
    tmp = s->dptr;
680
0
  }
681
682
0
  if (isdigit((unsigned char) *tmp))
683
0
  {
684
    /* range maximum */
685
0
    pat->max = strtol(tmp, &tmp, 0);
686
0
    if (toupper((unsigned char) *tmp) == 'K')
687
0
    {
688
0
      pat->max *= 1024;
689
0
      tmp++;
690
0
    }
691
0
    else if (toupper((unsigned char) *tmp) == 'M')
692
0
    {
693
0
      pat->max *= 1048576;
694
0
      tmp++;
695
0
    }
696
0
    if (do_exclusive)
697
0
      (pat->max)--;
698
0
  }
699
0
  else
700
0
  {
701
0
    pat->max = MUTT_MAXRANGE;
702
0
  }
703
704
0
  if (skip_quote && (*tmp == '"'))
705
0
    tmp++;
706
707
0
  SKIPWS(tmp);
708
0
  s->dptr = tmp;
709
0
  return true;
710
0
}
711
712
/**
713
 * eat_date - Parse a date pattern - Implements ::eat_arg_t - @ingroup eat_arg_api
714
 */
715
static bool eat_date(struct Pattern *pat, PatternCompFlags flags,
716
                     struct Buffer *s, struct Buffer *err)
717
0
{
718
0
  struct Buffer *tmp = buf_pool_get();
719
0
  bool rc = false;
720
721
0
  char *pexpr = s->dptr;
722
0
  if (parse_extract_token(tmp, s, TOKEN_COMMENT | TOKEN_PATTERN) != 0)
723
0
  {
724
0
    buf_printf(err, _("Error in expression: %s"), pexpr);
725
0
    goto out;
726
0
  }
727
728
0
  if (buf_is_empty(tmp))
729
0
  {
730
0
    buf_addstr(err, _("Empty expression"));
731
0
    goto out;
732
0
  }
733
734
0
  if (flags & MUTT_PC_PATTERN_DYNAMIC)
735
0
  {
736
0
    pat->dynamic = true;
737
0
    pat->p.str = mutt_str_dup(tmp->data);
738
0
  }
739
740
0
  rc = eval_date_minmax(pat, tmp->data, err);
741
742
0
out:
743
0
  buf_pool_release(&tmp);
744
0
  return rc;
745
0
}
746
747
/**
748
 * find_matching_paren - Find the matching parenthesis
749
 * @param s string to search
750
 * @retval ptr
751
 * - Matching close parenthesis
752
 * - End of string NUL, if not found
753
 */
754
static /* const */ char *find_matching_paren(/* const */ char *s)
755
0
{
756
0
  int level = 1;
757
758
0
  for (; *s; s++)
759
0
  {
760
0
    if (*s == '(')
761
0
    {
762
0
      level++;
763
0
    }
764
0
    else if (*s == ')')
765
0
    {
766
0
      level--;
767
0
      if (level == 0)
768
0
        break;
769
0
    }
770
0
  }
771
0
  return s;
772
0
}
773
774
/**
775
 * mutt_pattern_free - Free a Pattern
776
 * @param[out] pat Pattern to free
777
 */
778
void mutt_pattern_free(struct PatternList **pat)
779
0
{
780
0
  if (!pat || !*pat)
781
0
    return;
782
783
0
  struct Pattern *np = SLIST_FIRST(*pat), *next = NULL;
784
785
0
  while (np)
786
0
  {
787
0
    next = SLIST_NEXT(np, entries);
788
789
0
    if (np->is_multi)
790
0
    {
791
0
      mutt_list_free(&np->p.multi_cases);
792
0
    }
793
0
    else if (np->string_match || np->dynamic)
794
0
    {
795
0
      FREE(&np->p.str);
796
0
    }
797
0
    else if (np->group_match)
798
0
    {
799
0
      np->p.group = NULL;
800
0
    }
801
0
    else if (np->p.regex)
802
0
    {
803
0
      regfree(np->p.regex);
804
0
      FREE(&np->p.regex);
805
0
    }
806
807
#ifdef USE_DEBUG_GRAPHVIZ
808
    FREE(&np->raw_pattern);
809
#endif
810
0
    mutt_pattern_free(&np->child);
811
0
    FREE(&np);
812
813
0
    np = next;
814
0
  }
815
816
0
  FREE(pat);
817
0
}
818
819
/**
820
 * mutt_pattern_new - Create a new Pattern
821
 * @retval ptr Newly created Pattern
822
 */
823
static struct Pattern *mutt_pattern_new(void)
824
0
{
825
0
  return mutt_mem_calloc(1, sizeof(struct Pattern));
826
0
}
827
828
/**
829
 * mutt_pattern_list_new - Create a new list containing a Pattern
830
 * @retval ptr Newly created list containing a single node with a Pattern
831
 */
832
static struct PatternList *mutt_pattern_list_new(void)
833
0
{
834
0
  struct PatternList *h = mutt_mem_calloc(1, sizeof(struct PatternList));
835
0
  SLIST_INIT(h);
836
0
  struct Pattern *p = mutt_pattern_new();
837
0
  SLIST_INSERT_HEAD(h, p, entries);
838
0
  return h;
839
0
}
840
841
/**
842
 * attach_leaf - Attach a Pattern to a Pattern List
843
 * @param list Pattern List to attach to
844
 * @param leaf Pattern to attach
845
 * @retval ptr Attached leaf
846
 */
847
static struct Pattern *attach_leaf(struct PatternList *list, struct Pattern *leaf)
848
0
{
849
0
  struct Pattern *last = NULL;
850
0
  SLIST_FOREACH(last, list, entries)
851
0
  {
852
    // TODO - or we could use a doubly-linked list
853
0
    if (!SLIST_NEXT(last, entries))
854
0
    {
855
0
      SLIST_NEXT(last, entries) = leaf;
856
0
      break;
857
0
    }
858
0
  }
859
0
  return leaf;
860
0
}
861
862
/**
863
 * attach_new_root - Create a new Pattern as a parent for a List
864
 * @param curlist Pattern List
865
 * @retval ptr First Pattern in the original List
866
 *
867
 * @note curlist will be altered to the new root Pattern
868
 */
869
static struct Pattern *attach_new_root(struct PatternList **curlist)
870
0
{
871
0
  struct PatternList *root = mutt_pattern_list_new();
872
0
  struct Pattern *leaf = SLIST_FIRST(root);
873
0
  leaf->child = *curlist;
874
0
  *curlist = root;
875
0
  return leaf;
876
0
}
877
878
/**
879
 * attach_new_leaf - Attach a new Pattern to a List
880
 * @param curlist Pattern List
881
 * @retval ptr New Pattern in the original List
882
 *
883
 * @note curlist may be altered
884
 */
885
static struct Pattern *attach_new_leaf(struct PatternList **curlist)
886
0
{
887
0
  if (*curlist)
888
0
  {
889
0
    return attach_leaf(*curlist, mutt_pattern_new());
890
0
  }
891
0
  else
892
0
  {
893
0
    return attach_new_root(curlist);
894
0
  }
895
0
}
896
897
/**
898
 * mutt_pattern_comp - Create a Pattern
899
 * @param mv    Mailbox view
900
 * @param menu  Current Menu
901
 * @param s     Pattern string
902
 * @param flags Flags, e.g. #MUTT_PC_FULL_MSG
903
 * @param err   Buffer for error messages
904
 * @retval ptr Newly allocated Pattern
905
 */
906
struct PatternList *mutt_pattern_comp(struct MailboxView *mv, struct Menu *menu,
907
                                      const char *s, PatternCompFlags flags,
908
                                      struct Buffer *err)
909
0
{
910
  /* curlist when assigned will always point to a list containing at least one node
911
   * with a Pattern value.  */
912
0
  struct PatternList *curlist = NULL;
913
0
  bool pat_not = false;
914
0
  bool all_addr = false;
915
0
  bool pat_or = false;
916
0
  bool implicit = true; /* used to detect logical AND operator */
917
0
  bool is_alias = false;
918
0
  const struct PatternFlags *entry = NULL;
919
0
  char *p = NULL;
920
0
  char *buf = NULL;
921
0
  struct Buffer ps;
922
0
  struct Mailbox *m = mv ? mv->mailbox : NULL;
923
924
0
  if (!s || (s[0] == '\0'))
925
0
  {
926
0
    buf_strcpy(err, _("empty pattern"));
927
0
    return NULL;
928
0
  }
929
930
0
  buf_init(&ps);
931
0
  ps.dptr = (char *) s;
932
0
  ps.dsize = mutt_str_len(s);
933
934
0
  while (*ps.dptr)
935
0
  {
936
0
    SKIPWS(ps.dptr);
937
0
    switch (*ps.dptr)
938
0
    {
939
0
      case '^':
940
0
        ps.dptr++;
941
0
        all_addr = !all_addr;
942
0
        break;
943
0
      case '!':
944
0
        ps.dptr++;
945
0
        pat_not = !pat_not;
946
0
        break;
947
0
      case '@':
948
0
        ps.dptr++;
949
0
        is_alias = !is_alias;
950
0
        break;
951
0
      case '|':
952
0
        if (!pat_or)
953
0
        {
954
0
          if (!curlist)
955
0
          {
956
0
            buf_printf(err, _("error in pattern at: %s"), ps.dptr);
957
0
            return NULL;
958
0
          }
959
960
0
          struct Pattern *pat = SLIST_FIRST(curlist);
961
0
          if (SLIST_NEXT(pat, entries))
962
0
          {
963
            /* A & B | C == (A & B) | C */
964
0
            struct Pattern *root = attach_new_root(&curlist);
965
0
            root->op = MUTT_PAT_AND;
966
0
          }
967
968
0
          pat_or = true;
969
0
        }
970
0
        ps.dptr++;
971
0
        implicit = false;
972
0
        pat_not = false;
973
0
        all_addr = false;
974
0
        is_alias = false;
975
0
        break;
976
0
      case '%':
977
0
      case '=':
978
0
      case '~':
979
0
      {
980
0
        if (ps.dptr[1] == '\0')
981
0
        {
982
0
          buf_printf(err, _("missing pattern: %s"), ps.dptr);
983
0
          goto cleanup;
984
0
        }
985
0
        short thread_op = 0;
986
0
        if (ps.dptr[1] == '(')
987
0
          thread_op = MUTT_PAT_THREAD;
988
0
        else if ((ps.dptr[1] == '<') && (ps.dptr[2] == '('))
989
0
          thread_op = MUTT_PAT_PARENT;
990
0
        else if ((ps.dptr[1] == '>') && (ps.dptr[2] == '('))
991
0
          thread_op = MUTT_PAT_CHILDREN;
992
0
        if (thread_op != 0)
993
0
        {
994
0
          ps.dptr++; /* skip ~ */
995
0
          if ((thread_op == MUTT_PAT_PARENT) || (thread_op == MUTT_PAT_CHILDREN))
996
0
            ps.dptr++;
997
0
          p = find_matching_paren(ps.dptr + 1);
998
0
          if (p[0] != ')')
999
0
          {
1000
0
            buf_printf(err, _("mismatched parentheses: %s"), ps.dptr);
1001
0
            goto cleanup;
1002
0
          }
1003
0
          struct Pattern *leaf = attach_new_leaf(&curlist);
1004
0
          leaf->op = thread_op;
1005
0
          leaf->pat_not = pat_not;
1006
0
          leaf->all_addr = all_addr;
1007
0
          leaf->is_alias = is_alias;
1008
0
          pat_not = false;
1009
0
          all_addr = false;
1010
0
          is_alias = false;
1011
          /* compile the sub-expression */
1012
0
          buf = mutt_strn_dup(ps.dptr + 1, p - (ps.dptr + 1));
1013
0
          leaf->child = mutt_pattern_comp(mv, menu, buf, flags, err);
1014
0
          if (!leaf->child)
1015
0
          {
1016
0
            FREE(&buf);
1017
0
            goto cleanup;
1018
0
          }
1019
0
          FREE(&buf);
1020
0
          ps.dptr = p + 1; /* restore location */
1021
0
          SKIPWS(ps.dptr);
1022
0
          break;
1023
0
        }
1024
0
        if (implicit && pat_or)
1025
0
        {
1026
          /* A | B & C == (A | B) & C */
1027
0
          struct Pattern *root = attach_new_root(&curlist);
1028
0
          root->op = MUTT_PAT_OR;
1029
0
          pat_or = false;
1030
0
        }
1031
1032
0
        entry = lookup_tag(ps.dptr[1]);
1033
0
        if (!entry)
1034
0
        {
1035
0
          buf_printf(err, _("%c: invalid pattern modifier"), *ps.dptr);
1036
0
          goto cleanup;
1037
0
        }
1038
0
        if (entry->flags && ((flags & entry->flags) == 0))
1039
0
        {
1040
0
          buf_printf(err, _("%c: not supported in this mode"), *ps.dptr);
1041
0
          goto cleanup;
1042
0
        }
1043
1044
0
        struct Pattern *leaf = attach_new_leaf(&curlist);
1045
0
        leaf->pat_not = pat_not;
1046
0
        leaf->all_addr = all_addr;
1047
0
        leaf->is_alias = is_alias;
1048
0
        leaf->string_match = (ps.dptr[0] == '=');
1049
0
        leaf->group_match = (ps.dptr[0] == '%');
1050
0
        leaf->sendmode = (flags & MUTT_PC_SEND_MODE_SEARCH);
1051
0
        leaf->op = entry->op;
1052
0
        pat_not = false;
1053
0
        all_addr = false;
1054
0
        is_alias = false;
1055
1056
0
        ps.dptr++; /* move past the ~ */
1057
0
        ps.dptr++; /* eat the operator and any optional whitespace */
1058
0
        SKIPWS(ps.dptr);
1059
0
        if (entry->eat_arg)
1060
0
        {
1061
0
          if (ps.dptr[0] == '\0')
1062
0
          {
1063
0
            buf_addstr(err, _("missing parameter"));
1064
0
            goto cleanup;
1065
0
          }
1066
0
          switch (entry->eat_arg)
1067
0
          {
1068
0
            case EAT_REGEX:
1069
0
              if (!eat_regex(leaf, flags, &ps, err))
1070
0
                goto cleanup;
1071
0
              break;
1072
0
            case EAT_DATE:
1073
0
              if (!eat_date(leaf, flags, &ps, err))
1074
0
                goto cleanup;
1075
0
              break;
1076
0
            case EAT_RANGE:
1077
0
              if (!eat_range(leaf, flags, &ps, err))
1078
0
                goto cleanup;
1079
0
              break;
1080
0
            case EAT_MESSAGE_RANGE:
1081
0
              if (!eat_message_range(leaf, flags, &ps, err, mv))
1082
0
                goto cleanup;
1083
0
              break;
1084
0
            case EAT_QUERY:
1085
0
              if (!eat_query(leaf, flags, &ps, err, m))
1086
0
                goto cleanup;
1087
0
              break;
1088
0
            default:
1089
0
              break;
1090
0
          }
1091
0
        }
1092
0
        implicit = true;
1093
0
        break;
1094
0
      }
1095
1096
0
      case '(':
1097
0
      {
1098
0
        p = find_matching_paren(ps.dptr + 1);
1099
0
        if (p[0] != ')')
1100
0
        {
1101
0
          buf_printf(err, _("mismatched parentheses: %s"), ps.dptr);
1102
0
          goto cleanup;
1103
0
        }
1104
        /* compile the sub-expression */
1105
0
        buf = mutt_strn_dup(ps.dptr + 1, p - (ps.dptr + 1));
1106
0
        struct PatternList *sub = mutt_pattern_comp(mv, menu, buf, flags, err);
1107
0
        FREE(&buf);
1108
0
        if (!sub)
1109
0
          goto cleanup;
1110
0
        struct Pattern *leaf = SLIST_FIRST(sub);
1111
0
        if (curlist)
1112
0
        {
1113
0
          attach_leaf(curlist, leaf);
1114
0
          FREE(&sub);
1115
0
        }
1116
0
        else
1117
0
        {
1118
0
          curlist = sub;
1119
0
        }
1120
0
        leaf->pat_not ^= pat_not;
1121
0
        leaf->all_addr |= all_addr;
1122
0
        leaf->is_alias |= is_alias;
1123
0
        pat_not = false;
1124
0
        all_addr = false;
1125
0
        is_alias = false;
1126
0
        ps.dptr = p + 1; /* restore location */
1127
0
        SKIPWS(ps.dptr);
1128
0
        break;
1129
0
      }
1130
1131
0
      default:
1132
0
        buf_printf(err, _("error in pattern at: %s"), ps.dptr);
1133
0
        goto cleanup;
1134
0
    }
1135
0
  }
1136
1137
0
  if (!curlist)
1138
0
  {
1139
0
    buf_strcpy(err, _("empty pattern"));
1140
0
    return NULL;
1141
0
  }
1142
1143
0
  if (SLIST_NEXT(SLIST_FIRST(curlist), entries))
1144
0
  {
1145
0
    struct Pattern *root = attach_new_root(&curlist);
1146
0
    root->op = pat_or ? MUTT_PAT_OR : MUTT_PAT_AND;
1147
0
  }
1148
1149
0
  return curlist;
1150
1151
0
cleanup:
1152
0
  mutt_pattern_free(&curlist);
1153
0
  return NULL;
1154
0
}