LCOV - code coverage report
Current view: top level - pattern - compile.c (source / functions) Hit Total Coverage
Test: coverage.info Lines: 169 679 24.9 %
Date: 2022-03-09 12:17:43 Functions: 9 25 36.0 %

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

Generated by: LCOV version 1.14