/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 | } |