Line data Source code
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 "mutt.h"
46 : #include "lib.h"
47 : #include "menu/lib.h"
48 : #include "context.h"
49 : #include "init.h"
50 :
51 : // clang-format off
52 : typedef uint16_t ParseDateRangeFlags; ///< Flags for parse_date_range(), e.g. #MUTT_PDR_MINUS
53 : #define MUTT_PDR_NO_FLAGS 0 ///< No flags are set
54 : #define MUTT_PDR_MINUS (1 << 0) ///< Pattern contains a range
55 : #define MUTT_PDR_PLUS (1 << 1) ///< Extend the range using '+'
56 : #define MUTT_PDR_WINDOW (1 << 2) ///< Extend the range in both directions using '*'
57 : #define MUTT_PDR_ABSOLUTE (1 << 3) ///< Absolute pattern range
58 : #define MUTT_PDR_DONE (1 << 4) ///< Pattern parse successfully
59 : #define MUTT_PDR_ERROR (1 << 8) ///< Invalid pattern
60 : // clang-format on
61 :
62 : #define MUTT_PDR_ERRORDONE (MUTT_PDR_ERROR | MUTT_PDR_DONE)
63 :
64 : /**
65 : * enum EatRangeError - Error codes for eat_range_by_regex()
66 : */
67 : enum EatRangeError
68 : {
69 : RANGE_E_OK, ///< Range is valid
70 : RANGE_E_SYNTAX, ///< Range contains syntax error
71 : RANGE_E_CTX, ///< Range requires Context, but none available
72 : };
73 :
74 : #define KILO 1024
75 : #define MEGA 1048576
76 :
77 : /**
78 : * eat_regex - Parse a regex - Implements ::eat_arg_t - @ingroup eat_arg_api
79 : */
80 28 : static bool eat_regex(struct Pattern *pat, PatternCompFlags flags,
81 : struct Buffer *s, struct Buffer *err)
82 : {
83 28 : struct Buffer *buf = mutt_buffer_pool_get();
84 28 : bool rc = false;
85 28 : char *pexpr = s->dptr;
86 28 : if ((mutt_extract_token(buf, s, MUTT_TOKEN_PATTERN | MUTT_TOKEN_COMMENT) != 0) || !buf->data)
87 : {
88 0 : mutt_buffer_printf(err, _("Error in expression: %s"), pexpr);
89 0 : goto out;
90 : }
91 28 : if (buf->data[0] == '\0')
92 : {
93 0 : mutt_buffer_printf(err, "%s", _("Empty expression"));
94 0 : goto out;
95 : }
96 :
97 28 : if (pat->string_match)
98 : {
99 28 : pat->p.str = mutt_str_dup(buf->data);
100 28 : pat->ign_case = mutt_mb_is_lower(buf->data);
101 : }
102 0 : else if (pat->group_match)
103 : {
104 0 : pat->p.group = mutt_pattern_group(buf->data);
105 : }
106 : else
107 : {
108 0 : pat->p.regex = mutt_mem_calloc(1, sizeof(regex_t));
109 : #ifdef USE_DEBUG_GRAPHVIZ
110 : pat->raw_pattern = mutt_str_dup(buf->data);
111 : #endif
112 0 : uint16_t case_flags = mutt_mb_is_lower(buf->data) ? REG_ICASE : 0;
113 0 : int rc = REG_COMP(pat->p.regex, buf->data, REG_NEWLINE | REG_NOSUB | case_flags);
114 0 : if (rc != 0)
115 : {
116 : char errmsg[256];
117 0 : regerror(rc, pat->p.regex, errmsg, sizeof(errmsg));
118 0 : mutt_buffer_add_printf(err, "'%s': %s", buf->data, errmsg);
119 0 : FREE(&pat->p.regex);
120 0 : goto out;
121 : }
122 : }
123 :
124 28 : rc = true;
125 :
126 28 : out:
127 28 : mutt_buffer_pool_release(&buf);
128 28 : return rc;
129 : }
130 :
131 : /**
132 : * add_query_msgid - Parse a Message-Id and add it to a list - Implements ::mutt_file_map_t - @ingroup mutt_file_map_api
133 : * @retval true Always
134 : */
135 0 : static bool add_query_msgid(char *line, int line_num, void *user_data)
136 : {
137 0 : struct ListHead *msgid_list = (struct ListHead *) (user_data);
138 0 : char *nows = mutt_str_skip_whitespace(line);
139 0 : if (*nows == '\0')
140 0 : return true;
141 0 : mutt_str_remove_trailing_ws(nows);
142 0 : mutt_list_insert_tail(msgid_list, mutt_str_dup(nows));
143 0 : return true;
144 : }
145 :
146 : /**
147 : * eat_query - Parse a query for an external search program - Implements ::eat_arg_t - @ingroup eat_arg_api
148 : * @param pat Pattern to store the results in
149 : * @param flags Flags, e.g. #MUTT_PC_PATTERN_DYNAMIC
150 : * @param s String to parse
151 : * @param err Buffer for error messages
152 : * @param m Mailbox
153 : * @retval true The pattern was read successfully
154 : */
155 0 : static bool eat_query(struct Pattern *pat, PatternCompFlags flags,
156 : struct Buffer *s, struct Buffer *err, struct Mailbox *m)
157 : {
158 0 : struct Buffer *cmd_buf = mutt_buffer_pool_get();
159 0 : struct Buffer *tok_buf = mutt_buffer_pool_get();
160 0 : bool rc = false;
161 :
162 0 : FILE *fp = NULL;
163 :
164 : const char *const c_external_search_command =
165 0 : cs_subset_string(NeoMutt->sub, "external_search_command");
166 0 : if (!c_external_search_command)
167 : {
168 0 : mutt_buffer_printf(err, "%s", _("No search command defined"));
169 0 : goto out;
170 : }
171 :
172 0 : char *pexpr = s->dptr;
173 0 : if ((mutt_extract_token(tok_buf, s, MUTT_TOKEN_PATTERN | MUTT_TOKEN_COMMENT) != 0) ||
174 0 : !tok_buf->data)
175 : {
176 0 : mutt_buffer_printf(err, _("Error in expression: %s"), pexpr);
177 0 : goto out;
178 : }
179 0 : if (*tok_buf->data == '\0')
180 : {
181 0 : mutt_buffer_printf(err, "%s", _("Empty expression"));
182 0 : goto out;
183 : }
184 :
185 0 : mutt_buffer_addstr(cmd_buf, c_external_search_command);
186 0 : mutt_buffer_addch(cmd_buf, ' ');
187 :
188 0 : if (m)
189 : {
190 0 : char *escaped_folder = mutt_path_escape(mailbox_path(m));
191 0 : mutt_debug(LL_DEBUG2, "escaped folder path: %s\n", escaped_folder);
192 0 : mutt_buffer_addch(cmd_buf, '\'');
193 0 : mutt_buffer_addstr(cmd_buf, escaped_folder);
194 0 : mutt_buffer_addch(cmd_buf, '\'');
195 : }
196 : else
197 : {
198 0 : mutt_buffer_addch(cmd_buf, '/');
199 : }
200 0 : mutt_buffer_addch(cmd_buf, ' ');
201 0 : mutt_buffer_addstr(cmd_buf, tok_buf->data);
202 :
203 0 : mutt_message(_("Running search command: %s ..."), cmd_buf->data);
204 0 : pat->is_multi = true;
205 0 : mutt_list_clear(&pat->p.multi_cases);
206 0 : pid_t pid = filter_create(cmd_buf->data, NULL, &fp, NULL);
207 0 : if (pid < 0)
208 : {
209 0 : mutt_buffer_printf(err, "unable to fork command: %s\n", cmd_buf->data);
210 0 : goto out;
211 : }
212 :
213 0 : mutt_file_map_lines(add_query_msgid, &pat->p.multi_cases, fp, MUTT_RL_NO_FLAGS);
214 0 : mutt_file_fclose(&fp);
215 0 : filter_wait(pid);
216 :
217 0 : rc = true;
218 :
219 0 : out:
220 0 : mutt_buffer_pool_release(&cmd_buf);
221 0 : mutt_buffer_pool_release(&tok_buf);
222 0 : return rc;
223 : }
224 :
225 : /**
226 : * get_offset - Calculate a symbolic offset
227 : * @param tm Store the time here
228 : * @param s string to parse
229 : * @param sign Sign of range, 1 for positive, -1 for negative
230 : * @retval ptr Next char after parsed offset
231 : *
232 : * - Ny years
233 : * - Nm months
234 : * - Nw weeks
235 : * - Nd days
236 : */
237 0 : static const char *get_offset(struct tm *tm, const char *s, int sign)
238 : {
239 0 : char *ps = NULL;
240 0 : int offset = strtol(s, &ps, 0);
241 0 : if (((sign < 0) && (offset > 0)) || ((sign > 0) && (offset < 0)))
242 0 : offset = -offset;
243 :
244 0 : switch (*ps)
245 : {
246 0 : case 'y':
247 0 : tm->tm_year += offset;
248 0 : break;
249 0 : case 'm':
250 0 : tm->tm_mon += offset;
251 0 : break;
252 0 : case 'w':
253 0 : tm->tm_mday += 7 * offset;
254 0 : break;
255 0 : case 'd':
256 0 : tm->tm_mday += offset;
257 0 : break;
258 0 : case 'H':
259 0 : tm->tm_hour += offset;
260 0 : break;
261 0 : case 'M':
262 0 : tm->tm_min += offset;
263 0 : break;
264 0 : case 'S':
265 0 : tm->tm_sec += offset;
266 0 : break;
267 0 : default:
268 0 : return s;
269 : }
270 0 : mutt_date_normalize_time(tm);
271 0 : return ps + 1;
272 : }
273 :
274 : /**
275 : * get_date - Parse a (partial) date in dd/mm/yyyy format
276 : * @param s String to parse
277 : * @param t Store the time here
278 : * @param err Buffer for error messages
279 : * @retval ptr First character after the date
280 : *
281 : * This function parses a (partial) date separated by '/'. The month and year
282 : * are optional and if the year is less than 70 it's assumed to be after 2000.
283 : *
284 : * Examples:
285 : * - "10" = 10 of this month, this year
286 : * - "10/12" = 10 of December, this year
287 : * - "10/12/04" = 10 of December, 2004
288 : * - "10/12/2008" = 10 of December, 2008
289 : * - "20081210" = 10 of December, 2008
290 : */
291 0 : static const char *get_date(const char *s, struct tm *t, struct Buffer *err)
292 : {
293 0 : char *p = NULL;
294 0 : struct tm tm = mutt_date_localtime(MUTT_DATE_NOW);
295 0 : bool iso8601 = true;
296 :
297 0 : for (int v = 0; v < 8; v++)
298 : {
299 0 : if (s[v] && (s[v] >= '0') && (s[v] <= '9'))
300 0 : continue;
301 :
302 0 : iso8601 = false;
303 0 : break;
304 : }
305 :
306 0 : if (iso8601)
307 : {
308 0 : int year = 0;
309 0 : int month = 0;
310 0 : int mday = 0;
311 0 : sscanf(s, "%4d%2d%2d", &year, &month, &mday);
312 :
313 0 : t->tm_year = year;
314 0 : if (t->tm_year > 1900)
315 0 : t->tm_year -= 1900;
316 0 : t->tm_mon = month - 1;
317 0 : t->tm_mday = mday;
318 :
319 0 : if ((t->tm_mday < 1) || (t->tm_mday > 31))
320 : {
321 0 : snprintf(err->data, err->dsize, _("Invalid day of month: %s"), s);
322 0 : return NULL;
323 : }
324 0 : if ((t->tm_mon < 0) || (t->tm_mon > 11))
325 : {
326 0 : snprintf(err->data, err->dsize, _("Invalid month: %s"), s);
327 0 : return NULL;
328 : }
329 :
330 0 : return (s + 8);
331 : }
332 :
333 0 : t->tm_mday = strtol(s, &p, 10);
334 0 : if ((t->tm_mday < 1) || (t->tm_mday > 31))
335 : {
336 0 : mutt_buffer_printf(err, _("Invalid day of month: %s"), s);
337 0 : return NULL;
338 : }
339 0 : if (*p != '/')
340 : {
341 : /* fill in today's month and year */
342 0 : t->tm_mon = tm.tm_mon;
343 0 : t->tm_year = tm.tm_year;
344 0 : return p;
345 : }
346 0 : p++;
347 0 : t->tm_mon = strtol(p, &p, 10) - 1;
348 0 : if ((t->tm_mon < 0) || (t->tm_mon > 11))
349 : {
350 0 : mutt_buffer_printf(err, _("Invalid month: %s"), p);
351 0 : return NULL;
352 : }
353 0 : if (*p != '/')
354 : {
355 0 : t->tm_year = tm.tm_year;
356 0 : return p;
357 : }
358 0 : p++;
359 0 : t->tm_year = strtol(p, &p, 10);
360 0 : if (t->tm_year < 70) /* year 2000+ */
361 0 : t->tm_year += 100;
362 0 : else if (t->tm_year > 1900)
363 0 : t->tm_year -= 1900;
364 0 : return p;
365 : }
366 :
367 : /**
368 : * parse_date_range - Parse a date range
369 : * @param pc String to parse
370 : * @param min Earlier date
371 : * @param max Later date
372 : * @param have_min Do we have a base minimum date?
373 : * @param base_min Base minimum date
374 : * @param err Buffer for error messages
375 : * @retval ptr First character after the date
376 : */
377 0 : static const char *parse_date_range(const char *pc, struct tm *min, struct tm *max,
378 : bool have_min, struct tm *base_min, struct Buffer *err)
379 : {
380 0 : ParseDateRangeFlags flags = MUTT_PDR_NO_FLAGS;
381 0 : while (*pc && ((flags & MUTT_PDR_DONE) == 0))
382 : {
383 0 : const char *pt = NULL;
384 0 : char ch = *pc++;
385 0 : SKIPWS(pc);
386 0 : switch (ch)
387 : {
388 0 : case '-':
389 : {
390 : /* try a range of absolute date minus offset of Ndwmy */
391 0 : pt = get_offset(min, pc, -1);
392 0 : if (pc == pt)
393 : {
394 0 : if (flags == MUTT_PDR_NO_FLAGS)
395 : { /* nothing yet and no offset parsed => absolute date? */
396 0 : if (!get_date(pc, max, err))
397 0 : flags |= (MUTT_PDR_ABSOLUTE | MUTT_PDR_ERRORDONE); /* done bad */
398 : else
399 : {
400 : /* reestablish initial base minimum if not specified */
401 0 : if (!have_min)
402 0 : memcpy(min, base_min, sizeof(struct tm));
403 0 : flags |= (MUTT_PDR_ABSOLUTE | MUTT_PDR_DONE); /* done good */
404 : }
405 : }
406 : else
407 0 : flags |= MUTT_PDR_ERRORDONE;
408 : }
409 : else
410 : {
411 0 : pc = pt;
412 0 : if ((flags == MUTT_PDR_NO_FLAGS) && !have_min)
413 : { /* the very first "-3d" without a previous absolute date */
414 0 : max->tm_year = min->tm_year;
415 0 : max->tm_mon = min->tm_mon;
416 0 : max->tm_mday = min->tm_mday;
417 : }
418 0 : flags |= MUTT_PDR_MINUS;
419 : }
420 0 : break;
421 : }
422 0 : case '+':
423 : { /* enlarge plus range */
424 0 : pt = get_offset(max, pc, 1);
425 0 : if (pc == pt)
426 0 : flags |= MUTT_PDR_ERRORDONE;
427 : else
428 : {
429 0 : pc = pt;
430 0 : flags |= MUTT_PDR_PLUS;
431 : }
432 0 : break;
433 : }
434 0 : case '*':
435 : { /* enlarge window in both directions */
436 0 : pt = get_offset(min, pc, -1);
437 0 : if (pc == pt)
438 0 : flags |= MUTT_PDR_ERRORDONE;
439 : else
440 : {
441 0 : pc = get_offset(max, pc, 1);
442 0 : flags |= MUTT_PDR_WINDOW;
443 : }
444 0 : break;
445 : }
446 0 : default:
447 0 : flags |= MUTT_PDR_ERRORDONE;
448 : }
449 0 : SKIPWS(pc);
450 : }
451 0 : if ((flags & MUTT_PDR_ERROR) && !(flags & MUTT_PDR_ABSOLUTE))
452 : { /* get_date has its own error message, don't overwrite it here */
453 0 : mutt_buffer_printf(err, _("Invalid relative date: %s"), pc - 1);
454 : }
455 0 : return (flags & MUTT_PDR_ERROR) ? NULL : pc;
456 : }
457 :
458 : /**
459 : * adjust_date_range - Put a date range in the correct order
460 : * @param[in,out] min Earlier date
461 : * @param[in,out] max Later date
462 : */
463 0 : static void adjust_date_range(struct tm *min, struct tm *max)
464 : {
465 0 : if ((min->tm_year > max->tm_year) ||
466 0 : ((min->tm_year == max->tm_year) && (min->tm_mon > max->tm_mon)) ||
467 0 : ((min->tm_year == max->tm_year) && (min->tm_mon == max->tm_mon) &&
468 0 : (min->tm_mday > max->tm_mday)))
469 : {
470 : int tmp;
471 :
472 0 : tmp = min->tm_year;
473 0 : min->tm_year = max->tm_year;
474 0 : max->tm_year = tmp;
475 :
476 0 : tmp = min->tm_mon;
477 0 : min->tm_mon = max->tm_mon;
478 0 : max->tm_mon = tmp;
479 :
480 0 : tmp = min->tm_mday;
481 0 : min->tm_mday = max->tm_mday;
482 0 : max->tm_mday = tmp;
483 :
484 0 : min->tm_hour = 0;
485 0 : min->tm_min = 0;
486 0 : min->tm_sec = 0;
487 0 : max->tm_hour = 23;
488 0 : max->tm_min = 59;
489 0 : max->tm_sec = 59;
490 : }
491 0 : }
492 :
493 : /**
494 : * eval_date_minmax - Evaluate a date-range pattern against 'now'
495 : * @param pat Pattern to modify
496 : * @param s Pattern string to use
497 : * @param err Buffer for error messages
498 : * @retval true Pattern valid and updated
499 : * @retval false Pattern invalid
500 : */
501 0 : bool eval_date_minmax(struct Pattern *pat, const char *s, struct Buffer *err)
502 : {
503 : /* the '0' time is Jan 1, 1970 UTC, so in order to prevent a negative time
504 : * when doing timezone conversion, we use Jan 2, 1970 UTC as the base here */
505 0 : struct tm min = { 0 };
506 0 : min.tm_mday = 2;
507 0 : min.tm_year = 70;
508 :
509 : /* Arbitrary year in the future. Don't set this too high or
510 : * mutt_date_make_time() returns something larger than will fit in a time_t
511 : * on some systems */
512 0 : struct tm max = { 0 };
513 0 : max.tm_year = 130;
514 0 : max.tm_mon = 11;
515 0 : max.tm_mday = 31;
516 0 : max.tm_hour = 23;
517 0 : max.tm_min = 59;
518 0 : max.tm_sec = 59;
519 :
520 0 : if (strchr("<>=", s[0]))
521 : {
522 : /* offset from current time
523 : * <3d less than three days ago
524 : * >3d more than three days ago
525 : * =3d exactly three days ago */
526 0 : struct tm *tm = NULL;
527 0 : bool exact = false;
528 :
529 0 : if (s[0] == '<')
530 : {
531 0 : min = mutt_date_localtime(MUTT_DATE_NOW);
532 0 : tm = &min;
533 : }
534 : else
535 : {
536 0 : max = mutt_date_localtime(MUTT_DATE_NOW);
537 0 : tm = &max;
538 :
539 0 : if (s[0] == '=')
540 0 : exact = true;
541 : }
542 :
543 : /* Reset the HMS unless we are relative matching using one of those
544 : * offsets. */
545 0 : char *offset_type = NULL;
546 0 : strtol(s + 1, &offset_type, 0);
547 0 : if (!(*offset_type && strchr("HMS", *offset_type)))
548 : {
549 0 : tm->tm_hour = 23;
550 0 : tm->tm_min = 59;
551 0 : tm->tm_sec = 59;
552 : }
553 :
554 : /* force negative offset */
555 0 : get_offset(tm, s + 1, -1);
556 :
557 0 : if (exact)
558 : {
559 : /* start at the beginning of the day in question */
560 0 : memcpy(&min, &max, sizeof(max));
561 0 : min.tm_hour = 0;
562 0 : min.tm_sec = 0;
563 0 : min.tm_min = 0;
564 : }
565 : }
566 : else
567 : {
568 0 : const char *pc = s;
569 :
570 0 : bool have_min = false;
571 0 : bool until_now = false;
572 0 : if (isdigit((unsigned char) *pc))
573 : {
574 : /* minimum date specified */
575 0 : pc = get_date(pc, &min, err);
576 0 : if (!pc)
577 : {
578 0 : return false;
579 : }
580 0 : have_min = true;
581 0 : SKIPWS(pc);
582 0 : if (*pc == '-')
583 : {
584 0 : const char *pt = pc + 1;
585 0 : SKIPWS(pt);
586 0 : until_now = (*pt == '\0');
587 : }
588 : }
589 :
590 0 : if (!until_now)
591 : { /* max date or relative range/window */
592 :
593 : struct tm base_min;
594 :
595 0 : if (!have_min)
596 : { /* save base minimum and set current date, e.g. for "-3d+1d" */
597 0 : memcpy(&base_min, &min, sizeof(base_min));
598 0 : min = mutt_date_localtime(MUTT_DATE_NOW);
599 0 : min.tm_hour = 0;
600 0 : min.tm_sec = 0;
601 0 : min.tm_min = 0;
602 : }
603 :
604 : /* preset max date for relative offsets,
605 : * if nothing follows we search for messages on a specific day */
606 0 : max.tm_year = min.tm_year;
607 0 : max.tm_mon = min.tm_mon;
608 0 : max.tm_mday = min.tm_mday;
609 :
610 0 : if (!parse_date_range(pc, &min, &max, have_min, &base_min, err))
611 : { /* bail out on any parsing error */
612 0 : return false;
613 : }
614 : }
615 : }
616 :
617 : /* Since we allow two dates to be specified we'll have to adjust that. */
618 0 : adjust_date_range(&min, &max);
619 :
620 0 : pat->min = mutt_date_make_time(&min, true);
621 0 : pat->max = mutt_date_make_time(&max, true);
622 :
623 0 : return true;
624 : }
625 :
626 : /**
627 : * eat_range - Parse a number range - Implements ::eat_arg_t - @ingroup eat_arg_api
628 : */
629 0 : static bool eat_range(struct Pattern *pat, PatternCompFlags flags,
630 : struct Buffer *s, struct Buffer *err)
631 : {
632 0 : char *tmp = NULL;
633 0 : bool do_exclusive = false;
634 0 : bool skip_quote = false;
635 :
636 : /* If simple_search is set to "~m %s", the range will have double quotes
637 : * around it... */
638 0 : if (*s->dptr == '"')
639 : {
640 0 : s->dptr++;
641 0 : skip_quote = true;
642 : }
643 0 : if (*s->dptr == '<')
644 0 : do_exclusive = true;
645 0 : if ((*s->dptr != '-') && (*s->dptr != '<'))
646 : {
647 : /* range minimum */
648 0 : if (*s->dptr == '>')
649 : {
650 0 : pat->max = MUTT_MAXRANGE;
651 0 : pat->min = strtol(s->dptr + 1, &tmp, 0) + 1; /* exclusive range */
652 : }
653 : else
654 0 : pat->min = strtol(s->dptr, &tmp, 0);
655 0 : if (toupper((unsigned char) *tmp) == 'K') /* is there a prefix? */
656 : {
657 0 : pat->min *= 1024;
658 0 : tmp++;
659 : }
660 0 : else if (toupper((unsigned char) *tmp) == 'M')
661 : {
662 0 : pat->min *= 1048576;
663 0 : tmp++;
664 : }
665 0 : if (*s->dptr == '>')
666 : {
667 0 : s->dptr = tmp;
668 0 : return true;
669 : }
670 0 : if (*tmp != '-')
671 : {
672 : /* exact value */
673 0 : pat->max = pat->min;
674 0 : s->dptr = tmp;
675 0 : return true;
676 : }
677 0 : tmp++;
678 : }
679 : else
680 : {
681 0 : s->dptr++;
682 0 : tmp = s->dptr;
683 : }
684 :
685 0 : if (isdigit((unsigned char) *tmp))
686 : {
687 : /* range maximum */
688 0 : pat->max = strtol(tmp, &tmp, 0);
689 0 : if (toupper((unsigned char) *tmp) == 'K')
690 : {
691 0 : pat->max *= 1024;
692 0 : tmp++;
693 : }
694 0 : else if (toupper((unsigned char) *tmp) == 'M')
695 : {
696 0 : pat->max *= 1048576;
697 0 : tmp++;
698 : }
699 0 : if (do_exclusive)
700 0 : (pat->max)--;
701 : }
702 : else
703 0 : pat->max = MUTT_MAXRANGE;
704 :
705 0 : if (skip_quote && (*tmp == '"'))
706 0 : tmp++;
707 :
708 0 : SKIPWS(tmp);
709 0 : s->dptr = tmp;
710 0 : return true;
711 : }
712 :
713 : /**
714 : * report_regerror - Create a regex error message
715 : * @param regerr Regex error code
716 : * @param preg Regex pattern buffer
717 : * @param err Buffer for error messages
718 : * @retval #RANGE_E_SYNTAX Always
719 : */
720 0 : static int report_regerror(int regerr, regex_t *preg, struct Buffer *err)
721 : {
722 0 : size_t ds = err->dsize;
723 :
724 0 : if (regerror(regerr, preg, err->data, ds) > ds)
725 0 : mutt_debug(LL_DEBUG2, "warning: buffer too small for regerror\n");
726 : /* The return value is fixed, exists only to shorten code at callsite */
727 0 : return RANGE_E_SYNTAX;
728 : }
729 :
730 : /**
731 : * is_menu_available - Do we need a Context for this Pattern?
732 : * @param s String to check
733 : * @param pmatch Regex matches
734 : * @param kind Range type, e.g. #RANGE_K_REL
735 : * @param err Buffer for error messages
736 : * @param menu Current Menu
737 : * @retval false Context is required, but not available
738 : * @retval true Otherwise
739 : */
740 0 : static bool is_menu_available(struct Buffer *s, regmatch_t pmatch[], int kind,
741 : struct Buffer *err, struct Menu *menu)
742 : {
743 0 : const char *context_req_chars[] = {
744 : [RANGE_K_REL] = ".0123456789",
745 : [RANGE_K_ABS] = ".",
746 : [RANGE_K_LT] = "",
747 : [RANGE_K_GT] = "",
748 : [RANGE_K_BARE] = ".",
749 : };
750 :
751 : /* First decide if we're going to need the menu at all.
752 : * Relative patterns need it if they contain a dot or a number.
753 : * Absolute patterns only need it if they contain a dot. */
754 0 : char *context_loc = strpbrk(s->dptr + pmatch[0].rm_so, context_req_chars[kind]);
755 0 : if (!context_loc || (context_loc >= &s->dptr[pmatch[0].rm_eo]))
756 0 : return true;
757 :
758 : /* We need a current message. Do we actually have one? */
759 0 : if (menu)
760 0 : return true;
761 :
762 : /* Nope. */
763 0 : mutt_buffer_strcpy(err, _("No current message"));
764 0 : return false;
765 : }
766 :
767 : /**
768 : * scan_range_num - Parse a number range
769 : * @param s String to parse
770 : * @param pmatch Array of regex matches
771 : * @param group Index of regex match to use
772 : * @param kind Range type, e.g. #RANGE_K_REL
773 : * @param m Mailbox
774 : * @param menu Current Menu
775 : * @retval num Parse number
776 : */
777 0 : static int scan_range_num(struct Buffer *s, regmatch_t pmatch[], int group,
778 : int kind, struct Mailbox *m, struct Menu *menu)
779 : {
780 0 : int num = (int) strtol(&s->dptr[pmatch[group].rm_so], NULL, 0);
781 0 : unsigned char c = (unsigned char) (s->dptr[pmatch[group].rm_eo - 1]);
782 0 : if (toupper(c) == 'K')
783 0 : num *= KILO;
784 0 : else if (toupper(c) == 'M')
785 0 : num *= MEGA;
786 0 : switch (kind)
787 : {
788 0 : case RANGE_K_REL:
789 : {
790 0 : struct Email *e = mutt_get_virt_email(m, menu_get_index(menu));
791 0 : return num + EMSG(e);
792 : }
793 0 : case RANGE_K_LT:
794 0 : return num - 1;
795 0 : case RANGE_K_GT:
796 0 : return num + 1;
797 0 : default:
798 0 : return num;
799 : }
800 : }
801 :
802 : /**
803 : * scan_range_slot - Parse a range of message numbers
804 : * @param s String to parse
805 : * @param pmatch Regex matches
806 : * @param grp Which regex match to use
807 : * @param side Which side of the range is this? #RANGE_S_LEFT or #RANGE_S_RIGHT
808 : * @param kind Range type, e.g. #RANGE_K_REL
809 : * @param m Mailbox
810 : * @param menu Current Menu
811 : * @retval num Index number for the message specified
812 : */
813 0 : static int scan_range_slot(struct Buffer *s, regmatch_t pmatch[], int grp,
814 : int side, int kind, struct Mailbox *m, struct Menu *menu)
815 : {
816 : /* This means the left or right subpattern was empty, e.g. ",." */
817 0 : if ((pmatch[grp].rm_so == -1) || (pmatch[grp].rm_so == pmatch[grp].rm_eo))
818 : {
819 0 : if (side == RANGE_S_LEFT)
820 0 : return 1;
821 0 : if (side == RANGE_S_RIGHT)
822 0 : return m->msg_count;
823 : }
824 : /* We have something, so determine what */
825 0 : unsigned char c = (unsigned char) (s->dptr[pmatch[grp].rm_so]);
826 0 : switch (c)
827 : {
828 0 : case RANGE_CIRCUM:
829 0 : return 1;
830 0 : case RANGE_DOLLAR:
831 0 : return m->msg_count;
832 0 : case RANGE_DOT:
833 : {
834 0 : struct Email *e = mutt_get_virt_email(m, menu_get_index(menu));
835 0 : return EMSG(e);
836 : }
837 0 : case RANGE_LT:
838 : case RANGE_GT:
839 0 : return scan_range_num(s, pmatch, grp + 1, kind, m, menu);
840 0 : default:
841 : /* Only other possibility: a number */
842 0 : return scan_range_num(s, pmatch, grp, kind, m, menu);
843 : }
844 : }
845 :
846 : /**
847 : * order_range - Put a range in order
848 : * @param pat Pattern to check
849 : */
850 0 : static void order_range(struct Pattern *pat)
851 : {
852 0 : if (pat->min <= pat->max)
853 0 : return;
854 0 : int num = pat->min;
855 0 : pat->min = pat->max;
856 0 : pat->max = num;
857 : }
858 :
859 : /**
860 : * eat_range_by_regex - Parse a range given as a regex
861 : * @param pat Pattern to store the range in
862 : * @param s String to parse
863 : * @param kind Range type, e.g. #RANGE_K_REL
864 : * @param err Buffer for error messages
865 : * @param m Mailbox
866 : * @param menu Current Menu
867 : * @retval num EatRangeError code, e.g. #RANGE_E_OK
868 : */
869 0 : static int eat_range_by_regex(struct Pattern *pat, struct Buffer *s, int kind,
870 : struct Buffer *err, struct Mailbox *m, struct Menu *menu)
871 : {
872 : int regerr;
873 : regmatch_t pmatch[RANGE_RX_GROUPS];
874 0 : struct RangeRegex *pspec = &RangeRegexes[kind];
875 :
876 : /* First time through, compile the big regex */
877 0 : if (!pspec->ready)
878 : {
879 0 : regerr = regcomp(&pspec->cooked, pspec->raw, REG_EXTENDED);
880 0 : if (regerr != 0)
881 0 : return report_regerror(regerr, &pspec->cooked, err);
882 0 : pspec->ready = true;
883 : }
884 :
885 : /* Match the pattern buffer against the compiled regex.
886 : * No match means syntax error. */
887 0 : regerr = regexec(&pspec->cooked, s->dptr, RANGE_RX_GROUPS, pmatch, 0);
888 0 : if (regerr != 0)
889 0 : return report_regerror(regerr, &pspec->cooked, err);
890 :
891 0 : if (!is_menu_available(s, pmatch, kind, err, menu))
892 0 : return RANGE_E_CTX;
893 :
894 : /* Snarf the contents of the two sides of the range. */
895 0 : pat->min = scan_range_slot(s, pmatch, pspec->lgrp, RANGE_S_LEFT, kind, m, menu);
896 0 : pat->max = scan_range_slot(s, pmatch, pspec->rgrp, RANGE_S_RIGHT, kind, m, menu);
897 0 : mutt_debug(LL_DEBUG1, "pat->min=%d pat->max=%d\n", pat->min, pat->max);
898 :
899 : /* Special case for a bare 0. */
900 0 : if ((kind == RANGE_K_BARE) && (pat->min == 0) && (pat->max == 0))
901 : {
902 0 : if (!m || !menu)
903 : {
904 0 : mutt_buffer_strcpy(err, _("No current message"));
905 0 : return RANGE_E_CTX;
906 : }
907 0 : struct Email *e = mutt_get_virt_email(m, menu_get_index(menu));
908 0 : if (!e)
909 0 : return RANGE_E_CTX;
910 :
911 0 : pat->max = EMSG(e);
912 0 : pat->min = pat->max;
913 : }
914 :
915 : /* Since we don't enforce order, we must swap bounds if they're backward */
916 0 : order_range(pat);
917 :
918 : /* Slide pointer past the entire match. */
919 0 : s->dptr += pmatch[0].rm_eo;
920 0 : return RANGE_E_OK;
921 : }
922 :
923 : /**
924 : * eat_message_range - Parse a range of message numbers - Implements ::eat_arg_t - @ingroup eat_arg_api
925 : * @param pat Pattern to store the results in
926 : * @param flags Flags, e.g. #MUTT_PC_PATTERN_DYNAMIC
927 : * @param s String to parse
928 : * @param err Buffer for error messages
929 : * @param m Mailbox
930 : * @param menu Current Menu
931 : * @retval true The pattern was read successfully
932 : */
933 0 : static bool eat_message_range(struct Pattern *pat, PatternCompFlags flags,
934 : struct Buffer *s, struct Buffer *err,
935 : struct Mailbox *m, struct Menu *menu)
936 : {
937 0 : if (!m || !menu)
938 : {
939 : // We need these for pretty much anything
940 0 : mutt_buffer_strcpy(err, _("No Context"));
941 0 : return false;
942 : }
943 :
944 0 : bool skip_quote = false;
945 :
946 : /* If simple_search is set to "~m %s", the range will have double quotes
947 : * around it... */
948 0 : if (*s->dptr == '"')
949 : {
950 0 : s->dptr++;
951 0 : skip_quote = true;
952 : }
953 :
954 0 : for (int i_kind = 0; i_kind != RANGE_K_INVALID; i_kind++)
955 : {
956 0 : switch (eat_range_by_regex(pat, s, i_kind, err, m, menu))
957 : {
958 0 : case RANGE_E_CTX:
959 : /* This means it matched syntactically but lacked context.
960 : * No point in continuing. */
961 0 : break;
962 0 : case RANGE_E_SYNTAX:
963 : /* Try another syntax, then */
964 0 : continue;
965 0 : case RANGE_E_OK:
966 0 : if (skip_quote && (*s->dptr == '"'))
967 0 : s->dptr++;
968 0 : SKIPWS(s->dptr);
969 0 : return true;
970 : }
971 : }
972 0 : return false;
973 : }
974 :
975 : /**
976 : * eat_date - Parse a date pattern - Implements ::eat_arg_t - @ingroup eat_arg_api
977 : */
978 0 : static bool eat_date(struct Pattern *pat, PatternCompFlags flags,
979 : struct Buffer *s, struct Buffer *err)
980 : {
981 0 : struct Buffer *tmp = mutt_buffer_pool_get();
982 0 : bool rc = false;
983 :
984 :
985 0 : char *pexpr = s->dptr;
986 0 : if (mutt_extract_token(tmp, s, MUTT_TOKEN_COMMENT | MUTT_TOKEN_PATTERN) != 0)
987 : {
988 0 : snprintf(err->data, err->dsize, _("Error in expression: %s"), pexpr);
989 0 : goto out;
990 : }
991 :
992 0 : if (mutt_buffer_is_empty(tmp))
993 : {
994 0 : snprintf(err->data, err->dsize, "%s", _("Empty expression"));
995 0 : goto out;
996 : }
997 :
998 0 : if (flags & MUTT_PC_PATTERN_DYNAMIC)
999 : {
1000 0 : pat->dynamic = true;
1001 0 : pat->p.str = mutt_str_dup(tmp->data);
1002 : }
1003 :
1004 0 : rc = eval_date_minmax(pat, tmp->data, err);
1005 :
1006 0 : out:
1007 0 : mutt_buffer_pool_release(&tmp);
1008 0 : return rc;
1009 : }
1010 :
1011 : /**
1012 : * find_matching_paren - Find the matching parenthesis
1013 : * @param s string to search
1014 : * @retval ptr
1015 : * - Matching close parenthesis
1016 : * - End of string NUL, if not found
1017 : */
1018 6 : static /* const */ char *find_matching_paren(/* const */ char *s)
1019 : {
1020 6 : int level = 1;
1021 :
1022 84 : for (; *s; s++)
1023 : {
1024 84 : if (*s == '(')
1025 0 : level++;
1026 84 : else if (*s == ')')
1027 : {
1028 6 : level--;
1029 6 : if (level == 0)
1030 6 : break;
1031 : }
1032 : }
1033 6 : return s;
1034 : }
1035 :
1036 : /**
1037 : * mutt_pattern_free - Free a Pattern
1038 : * @param[out] pat Pattern to free
1039 : */
1040 84 : void mutt_pattern_free(struct PatternList **pat)
1041 : {
1042 84 : if (!pat || !*pat)
1043 44 : return;
1044 :
1045 40 : struct Pattern *np = SLIST_FIRST(*pat), *next = NULL;
1046 :
1047 102 : while (np)
1048 : {
1049 62 : next = SLIST_NEXT(np, entries);
1050 :
1051 62 : if (np->is_multi)
1052 0 : mutt_list_free(&np->p.multi_cases);
1053 62 : else if (np->string_match || np->dynamic)
1054 30 : FREE(&np->p.str);
1055 32 : else if (np->group_match)
1056 0 : np->p.group = NULL;
1057 32 : else if (np->p.regex)
1058 : {
1059 0 : regfree(np->p.regex);
1060 0 : FREE(&np->p.regex);
1061 : }
1062 :
1063 : #ifdef USE_DEBUG_GRAPHVIZ
1064 : FREE(&np->raw_pattern);
1065 : #endif
1066 62 : mutt_pattern_free(&np->child);
1067 62 : FREE(&np);
1068 :
1069 62 : np = next;
1070 : }
1071 :
1072 40 : FREE(pat);
1073 : }
1074 :
1075 : /**
1076 : * mutt_pattern_new - Create a new Pattern
1077 : * @retval ptr Newly created Pattern
1078 : */
1079 62 : static struct Pattern *mutt_pattern_new(void)
1080 : {
1081 62 : return mutt_mem_calloc(1, sizeof(struct Pattern));
1082 : }
1083 :
1084 : /**
1085 : * mutt_pattern_list_new - Create a new list containing a Pattern
1086 : * @retval ptr Newly created list containing a single node with a Pattern
1087 : */
1088 40 : static struct PatternList *mutt_pattern_list_new(void)
1089 : {
1090 40 : struct PatternList *h = mutt_mem_calloc(1, sizeof(struct PatternList));
1091 40 : SLIST_INIT(h);
1092 40 : struct Pattern *p = mutt_pattern_new();
1093 40 : SLIST_INSERT_HEAD(h, p, entries);
1094 40 : return h;
1095 : }
1096 :
1097 22 : static struct Pattern *attach_leaf(struct PatternList *list, struct Pattern *leaf)
1098 : {
1099 22 : struct Pattern *last = NULL;
1100 24 : SLIST_FOREACH(last, list, entries)
1101 : {
1102 : // TODO - or we could use a doubly-linked list
1103 24 : if (SLIST_NEXT(last, entries) == NULL)
1104 : {
1105 22 : SLIST_NEXT(last, entries) = leaf;
1106 22 : break;
1107 : }
1108 : }
1109 22 : return leaf;
1110 : }
1111 :
1112 40 : static struct Pattern *attach_new_root(struct PatternList **curlist)
1113 : {
1114 40 : struct PatternList *root = mutt_pattern_list_new();
1115 40 : struct Pattern *leaf = SLIST_FIRST(root);
1116 40 : leaf->child = *curlist;
1117 40 : *curlist = root;
1118 40 : return leaf;
1119 : }
1120 :
1121 :
1122 42 : static struct Pattern *attach_new_leaf(struct PatternList **curlist)
1123 : {
1124 42 : if (*curlist)
1125 : {
1126 22 : return attach_leaf(*curlist, mutt_pattern_new());
1127 : }
1128 : else
1129 : {
1130 20 : return attach_new_root(curlist);
1131 : }
1132 : }
1133 :
1134 : /**
1135 : * mutt_pattern_comp - Create a Pattern
1136 : * @param m Mailbox
1137 : * @param menu Current Menu
1138 : * @param s Pattern string
1139 : * @param flags Flags, e.g. #MUTT_PC_FULL_MSG
1140 : * @param err Buffer for error messages
1141 : * @retval ptr Newly allocated Pattern
1142 : */
1143 32 : struct PatternList *mutt_pattern_comp(struct Mailbox *m, struct Menu *menu, const char *s,
1144 : PatternCompFlags flags, struct Buffer *err)
1145 : {
1146 : /* curlist when assigned will always point to a list containing at least one node
1147 : * with a Pattern value. */
1148 32 : struct PatternList *curlist = NULL;
1149 32 : bool pat_not = false;
1150 32 : bool all_addr = false;
1151 32 : bool pat_or = false;
1152 32 : bool implicit = true; /* used to detect logical AND operator */
1153 32 : bool is_alias = false;
1154 32 : const struct PatternFlags *entry = NULL;
1155 32 : char *p = NULL;
1156 32 : char *buf = NULL;
1157 : struct Buffer ps;
1158 :
1159 32 : if (!s || (s[0] == '\0'))
1160 : {
1161 2 : mutt_buffer_strcpy(err, _("empty pattern"));
1162 2 : return NULL;
1163 : }
1164 :
1165 30 : mutt_buffer_init(&ps);
1166 30 : ps.dptr = (char *) s;
1167 30 : ps.dsize = mutt_str_len(s);
1168 :
1169 88 : while (*ps.dptr)
1170 : {
1171 72 : SKIPWS(ps.dptr);
1172 64 : switch (*ps.dptr)
1173 : {
1174 0 : case '^':
1175 0 : ps.dptr++;
1176 0 : all_addr = !all_addr;
1177 0 : break;
1178 6 : case '!':
1179 6 : ps.dptr++;
1180 6 : pat_not = !pat_not;
1181 6 : break;
1182 0 : case '@':
1183 0 : ps.dptr++;
1184 0 : is_alias = !is_alias;
1185 0 : break;
1186 8 : case '|':
1187 8 : if (!pat_or)
1188 : {
1189 8 : if (!curlist)
1190 : {
1191 2 : mutt_buffer_printf(err, _("error in pattern at: %s"), ps.dptr);
1192 2 : return NULL;
1193 : }
1194 :
1195 6 : struct Pattern *pat = SLIST_FIRST(curlist);
1196 6 : if (SLIST_NEXT(pat, entries))
1197 : {
1198 : /* A & B | C == (A & B) | C */
1199 2 : struct Pattern *root = attach_new_root(&curlist);
1200 2 : root->op = MUTT_PAT_AND;
1201 : }
1202 :
1203 6 : pat_or = true;
1204 : }
1205 6 : ps.dptr++;
1206 6 : implicit = false;
1207 6 : pat_not = false;
1208 6 : all_addr = false;
1209 6 : is_alias = false;
1210 6 : break;
1211 42 : case '%':
1212 : case '=':
1213 : case '~':
1214 : {
1215 42 : if (ps.dptr[1] == '\0')
1216 : {
1217 0 : mutt_buffer_printf(err, _("missing pattern: %s"), ps.dptr);
1218 0 : goto cleanup;
1219 : }
1220 42 : short thread_op = 0;
1221 42 : if (ps.dptr[1] == '(')
1222 0 : thread_op = MUTT_PAT_THREAD;
1223 42 : else if ((ps.dptr[1] == '<') && (ps.dptr[2] == '('))
1224 0 : thread_op = MUTT_PAT_PARENT;
1225 42 : else if ((ps.dptr[1] == '>') && (ps.dptr[2] == '('))
1226 0 : thread_op = MUTT_PAT_CHILDREN;
1227 42 : if (thread_op != 0)
1228 : {
1229 0 : ps.dptr++; /* skip ~ */
1230 0 : if ((thread_op == MUTT_PAT_PARENT) || (thread_op == MUTT_PAT_CHILDREN))
1231 0 : ps.dptr++;
1232 0 : p = find_matching_paren(ps.dptr + 1);
1233 0 : if (p[0] != ')')
1234 : {
1235 0 : mutt_buffer_printf(err, _("mismatched parentheses: %s"), ps.dptr);
1236 0 : goto cleanup;
1237 : }
1238 0 : struct Pattern *leaf = attach_new_leaf(&curlist);
1239 0 : leaf->op = thread_op;
1240 0 : leaf->pat_not = pat_not;
1241 0 : leaf->all_addr = all_addr;
1242 0 : leaf->is_alias = is_alias;
1243 0 : pat_not = false;
1244 0 : all_addr = false;
1245 0 : is_alias = false;
1246 : /* compile the sub-expression */
1247 0 : buf = mutt_strn_dup(ps.dptr + 1, p - (ps.dptr + 1));
1248 0 : leaf->child = mutt_pattern_comp(m, menu, buf, flags, err);
1249 0 : if (!leaf->child)
1250 : {
1251 0 : FREE(&buf);
1252 0 : goto cleanup;
1253 : }
1254 0 : FREE(&buf);
1255 0 : ps.dptr = p + 1; /* restore location */
1256 0 : SKIPWS(ps.dptr);
1257 0 : break;
1258 : }
1259 42 : if (implicit && pat_or)
1260 : {
1261 : /* A | B & C == (A | B) & C */
1262 2 : struct Pattern *root = attach_new_root(&curlist);
1263 2 : root->op = MUTT_PAT_OR;
1264 2 : pat_or = false;
1265 : }
1266 :
1267 42 : entry = lookup_tag(ps.dptr[1]);
1268 42 : if (!entry)
1269 : {
1270 0 : mutt_buffer_printf(err, _("%c: invalid pattern modifier"), *ps.dptr);
1271 0 : goto cleanup;
1272 : }
1273 42 : if (entry->flags && ((flags & entry->flags) == 0))
1274 : {
1275 0 : mutt_buffer_printf(err, _("%c: not supported in this mode"), *ps.dptr);
1276 0 : goto cleanup;
1277 : }
1278 :
1279 42 : struct Pattern *leaf = attach_new_leaf(&curlist);
1280 42 : leaf->pat_not = pat_not;
1281 42 : leaf->all_addr = all_addr;
1282 42 : leaf->is_alias = is_alias;
1283 42 : leaf->string_match = (ps.dptr[0] == '=');
1284 42 : leaf->group_match = (ps.dptr[0] == '%');
1285 42 : leaf->sendmode = (flags & MUTT_PC_SEND_MODE_SEARCH);
1286 42 : leaf->op = entry->op;
1287 42 : pat_not = false;
1288 42 : all_addr = false;
1289 42 : is_alias = false;
1290 :
1291 42 : ps.dptr++; /* move past the ~ */
1292 42 : ps.dptr++; /* eat the operator and any optional whitespace */
1293 78 : SKIPWS(ps.dptr);
1294 42 : if (entry->eat_arg)
1295 : {
1296 30 : if (ps.dptr[0] == '\0')
1297 : {
1298 2 : mutt_buffer_printf(err, "%s", _("missing parameter"));
1299 2 : goto cleanup;
1300 : }
1301 28 : switch (entry->eat_arg)
1302 : {
1303 28 : case EAT_REGEX:
1304 28 : if (!eat_regex(leaf, flags, &ps, err))
1305 0 : goto cleanup;
1306 28 : break;
1307 0 : case EAT_DATE:
1308 0 : if (!eat_date(leaf, flags, &ps, err))
1309 0 : goto cleanup;
1310 0 : break;
1311 0 : case EAT_RANGE:
1312 0 : if (!eat_range(leaf, flags, &ps, err))
1313 0 : goto cleanup;
1314 0 : break;
1315 0 : case EAT_MESSAGE_RANGE:
1316 0 : if (!eat_message_range(leaf, flags, &ps, err, m, menu))
1317 0 : goto cleanup;
1318 0 : break;
1319 0 : case EAT_QUERY:
1320 0 : if (!eat_query(leaf, flags, &ps, err, m))
1321 0 : goto cleanup;
1322 0 : break;
1323 0 : default:
1324 0 : break;
1325 : }
1326 : }
1327 40 : implicit = true;
1328 40 : break;
1329 : }
1330 :
1331 6 : case '(':
1332 : {
1333 6 : p = find_matching_paren(ps.dptr + 1);
1334 6 : if (p[0] != ')')
1335 : {
1336 0 : mutt_buffer_printf(err, _("mismatched parentheses: %s"), ps.dptr);
1337 0 : goto cleanup;
1338 : }
1339 : /* compile the sub-expression */
1340 6 : buf = mutt_strn_dup(ps.dptr + 1, p - (ps.dptr + 1));
1341 6 : struct PatternList *sub = mutt_pattern_comp(m, menu, buf, flags, err);
1342 6 : FREE(&buf);
1343 6 : if (!sub)
1344 0 : goto cleanup;
1345 6 : struct Pattern *leaf = SLIST_FIRST(sub);
1346 6 : if (curlist)
1347 : {
1348 0 : attach_leaf(curlist, leaf);
1349 0 : FREE(&sub);
1350 : }
1351 : else
1352 : {
1353 6 : curlist = sub;
1354 : }
1355 6 : leaf->pat_not ^= pat_not;
1356 6 : leaf->all_addr |= all_addr;
1357 6 : leaf->is_alias |= is_alias;
1358 6 : pat_not = false;
1359 6 : all_addr = false;
1360 6 : is_alias = false;
1361 6 : ps.dptr = p + 1; /* restore location */
1362 8 : SKIPWS(ps.dptr);
1363 6 : break;
1364 : }
1365 :
1366 2 : default:
1367 2 : mutt_buffer_printf(err, _("error in pattern at: %s"), ps.dptr);
1368 2 : goto cleanup;
1369 : }
1370 : }
1371 :
1372 24 : if (!curlist)
1373 : {
1374 0 : mutt_buffer_strcpy(err, _("empty pattern"));
1375 0 : return NULL;
1376 : }
1377 :
1378 24 : if (SLIST_NEXT(SLIST_FIRST(curlist), entries))
1379 : {
1380 16 : struct Pattern *root = attach_new_root(&curlist);
1381 16 : root->op = pat_or ? MUTT_PAT_OR : MUTT_PAT_AND;
1382 : }
1383 :
1384 24 : return curlist;
1385 :
1386 4 : cleanup:
1387 4 : mutt_pattern_free(&curlist);
1388 4 : return NULL;
1389 : }
|