1 /**
2  * @file
3  * Type representing a regular expression
4  *
5  * @authors
6  * Copyright (C) 2017-2018 Richard Russon <rich@flatcap.org>
7  *
8  * @copyright
9  * This program is free software: you can redistribute it and/or modify it under
10  * the terms of the GNU General Public License as published by the Free Software
11  * Foundation, either version 2 of the License, or (at your option) any later
12  * version.
13  *
14  * This program is distributed in the hope that it will be useful, but WITHOUT
15  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
16  * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
17  * details.
18  *
19  * You should have received a copy of the GNU General Public License along with
20  * this program.  If not, see <http://www.gnu.org/licenses/>.
21  */
22 
23 /**
24  * @page config_regex Type: Regular expression
25  *
26  * Config type representing a regular expression.
27  *
28  * - Backed by `struct Regex`
29  * - Empty regular expression is stored as `NULL`
30  * - Validator is passed `struct Regex`, which may be `NULL`
31  * - Data is freed when `ConfigSet` is freed
32  * - Implementation: #CstRegex
33  */
34 
35 #include "config.h"
36 #include <stddef.h>
37 #include <stdbool.h>
38 #include <stdint.h>
39 #include "mutt/lib.h"
40 #include "regex2.h"
41 #include "set.h"
42 #include "types.h"
43 
44 /**
45  * regex_free - Free a Regex object
46  * @param[out] r Regex to free
47  */
regex_free(struct Regex ** r)48 void regex_free(struct Regex **r)
49 {
50   if (!r || !*r)
51     return;
52 
53   FREE(&(*r)->pattern);
54   if ((*r)->regex)
55     regfree((*r)->regex);
56   FREE(&(*r)->regex);
57   FREE(r);
58 }
59 
60 /**
61  * regex_destroy - Destroy a Regex object - Implements ConfigSetType::destroy() - @ingroup cfg_type_destroy
62  */
regex_destroy(const struct ConfigSet * cs,void * var,const struct ConfigDef * cdef)63 static void regex_destroy(const struct ConfigSet *cs, void *var, const struct ConfigDef *cdef)
64 {
65   struct Regex **r = var;
66   if (!*r)
67     return;
68 
69   regex_free(r);
70 }
71 
72 /**
73  * regex_new - Create an Regex from a string
74  * @param str   Regular expression
75  * @param flags Type flags, e.g. #DT_REGEX_MATCH_CASE
76  * @param err   Buffer for error messages
77  * @retval ptr New Regex object
78  * @retval NULL Error
79  */
regex_new(const char * str,uint32_t flags,struct Buffer * err)80 struct Regex *regex_new(const char *str, uint32_t flags, struct Buffer *err)
81 {
82   if (!str)
83     return NULL;
84 
85   int rflags = 0;
86   struct Regex *reg = mutt_mem_calloc(1, sizeof(struct Regex));
87 
88   reg->regex = mutt_mem_calloc(1, sizeof(regex_t));
89   reg->pattern = mutt_str_dup(str);
90 
91   /* Should we use smart case matching? */
92   if (((flags & DT_REGEX_MATCH_CASE) == 0) && mutt_mb_is_lower(str))
93     rflags |= REG_ICASE;
94 
95   if ((flags & DT_REGEX_NOSUB))
96     rflags |= REG_NOSUB;
97 
98   /* Is a prefix of '!' allowed? */
99   if (((flags & DT_REGEX_ALLOW_NOT) != 0) && (str[0] == '!'))
100   {
101     reg->pat_not = true;
102     str++;
103   }
104 
105   int rc = REG_COMP(reg->regex, str, rflags);
106   if (rc != 0)
107   {
108     if (err)
109       regerror(rc, reg->regex, err->data, err->dsize);
110     regex_free(&reg);
111     return NULL;
112   }
113 
114   return reg;
115 }
116 
117 /**
118  * regex_string_set - Set a Regex by string - Implements ConfigSetType::string_set() - @ingroup cfg_type_string_set
119  */
regex_string_set(const struct ConfigSet * cs,void * var,struct ConfigDef * cdef,const char * value,struct Buffer * err)120 static int regex_string_set(const struct ConfigSet *cs, void *var, struct ConfigDef *cdef,
121                             const char *value, struct Buffer *err)
122 {
123   /* Store empty regexes as NULL */
124   if (value && (value[0] == '\0'))
125     value = NULL;
126 
127   struct Regex *r = NULL;
128 
129   int rc = CSR_SUCCESS;
130 
131   if (var)
132   {
133     struct Regex *curval = *(struct Regex **) var;
134     if (curval && mutt_str_equal(value, curval->pattern))
135       return CSR_SUCCESS | CSR_SUC_NO_CHANGE;
136 
137     if (value)
138     {
139       r = regex_new(value, cdef->type, err);
140       if (!r)
141         return CSR_ERR_INVALID;
142     }
143 
144     if (cdef->validator)
145     {
146       rc = cdef->validator(cs, cdef, (intptr_t) r, err);
147 
148       if (CSR_RESULT(rc) != CSR_SUCCESS)
149       {
150         regex_free(&r);
151         return rc | CSR_INV_VALIDATOR;
152       }
153     }
154 
155     regex_destroy(cs, var, cdef);
156 
157     *(struct Regex **) var = r;
158 
159     if (!r)
160       rc |= CSR_SUC_EMPTY;
161   }
162   else
163   {
164     if (cdef->type & DT_INITIAL_SET)
165       FREE(&cdef->initial);
166 
167     cdef->type |= DT_INITIAL_SET;
168     cdef->initial = (intptr_t) mutt_str_dup(value);
169   }
170 
171   return rc;
172 }
173 
174 /**
175  * regex_string_get - Get a Regex as a string - Implements ConfigSetType::string_get() - @ingroup cfg_type_string_get
176  */
regex_string_get(const struct ConfigSet * cs,void * var,const struct ConfigDef * cdef,struct Buffer * result)177 static int regex_string_get(const struct ConfigSet *cs, void *var,
178                             const struct ConfigDef *cdef, struct Buffer *result)
179 {
180   const char *str = NULL;
181 
182   if (var)
183   {
184     struct Regex *r = *(struct Regex **) var;
185     if (r)
186       str = r->pattern;
187   }
188   else
189   {
190     str = (char *) cdef->initial;
191   }
192 
193   if (!str)
194     return CSR_SUCCESS | CSR_SUC_EMPTY; /* empty string */
195 
196   mutt_buffer_addstr(result, str);
197   return CSR_SUCCESS;
198 }
199 
200 /**
201  * regex_native_set - Set a Regex config item by Regex object - Implements ConfigSetType::native_set() - @ingroup cfg_type_native_set
202  */
regex_native_set(const struct ConfigSet * cs,void * var,const struct ConfigDef * cdef,intptr_t value,struct Buffer * err)203 static int regex_native_set(const struct ConfigSet *cs, void *var,
204                             const struct ConfigDef *cdef, intptr_t value, struct Buffer *err)
205 {
206   int rc;
207 
208   if (cdef->validator)
209   {
210     rc = cdef->validator(cs, cdef, value, err);
211 
212     if (CSR_RESULT(rc) != CSR_SUCCESS)
213       return rc | CSR_INV_VALIDATOR;
214   }
215 
216   rc = CSR_SUCCESS;
217   struct Regex *orig = (struct Regex *) value;
218   struct Regex *r = NULL;
219 
220   if (orig && orig->pattern)
221   {
222     const uint32_t flags = orig->pat_not ? DT_REGEX_ALLOW_NOT : 0;
223     r = regex_new(orig->pattern, flags, err);
224     if (!r)
225       rc = CSR_ERR_INVALID;
226   }
227   else
228   {
229     rc |= CSR_SUC_EMPTY;
230   }
231 
232   if (CSR_RESULT(rc) == CSR_SUCCESS)
233   {
234     regex_free(var);
235     *(struct Regex **) var = r;
236   }
237 
238   return rc;
239 }
240 
241 /**
242  * regex_native_get - Get a Regex object from a Regex config item - Implements ConfigSetType::native_get() - @ingroup cfg_type_native_get
243  */
regex_native_get(const struct ConfigSet * cs,void * var,const struct ConfigDef * cdef,struct Buffer * err)244 static intptr_t regex_native_get(const struct ConfigSet *cs, void *var,
245                                  const struct ConfigDef *cdef, struct Buffer *err)
246 {
247   struct Regex *r = *(struct Regex **) var;
248 
249   return (intptr_t) r;
250 }
251 
252 /**
253  * regex_reset - Reset a Regex to its initial value - Implements ConfigSetType::reset() - @ingroup cfg_type_reset
254  */
regex_reset(const struct ConfigSet * cs,void * var,const struct ConfigDef * cdef,struct Buffer * err)255 static int regex_reset(const struct ConfigSet *cs, void *var,
256                        const struct ConfigDef *cdef, struct Buffer *err)
257 {
258   struct Regex *r = NULL;
259   const char *initial = (const char *) cdef->initial;
260 
261   struct Regex *currx = *(struct Regex **) var;
262   const char *curval = currx ? currx->pattern : NULL;
263 
264   int rc = CSR_SUCCESS;
265   if (!currx)
266     rc |= CSR_SUC_EMPTY;
267 
268   if (mutt_str_equal(initial, curval))
269     return rc | CSR_SUC_NO_CHANGE;
270 
271   if (initial)
272   {
273     r = regex_new(initial, cdef->type, err);
274     if (!r)
275       return CSR_ERR_CODE;
276   }
277 
278   if (cdef->validator)
279   {
280     rc = cdef->validator(cs, cdef, (intptr_t) r, err);
281 
282     if (CSR_RESULT(rc) != CSR_SUCCESS)
283     {
284       regex_destroy(cs, &r, cdef);
285       return rc | CSR_INV_VALIDATOR;
286     }
287   }
288 
289   if (!r)
290     rc |= CSR_SUC_EMPTY;
291 
292   regex_destroy(cs, var, cdef);
293 
294   *(struct Regex **) var = r;
295   return rc;
296 }
297 
298 /**
299  * CstRegex - Config type representing a regular expression
300  */
301 const struct ConfigSetType CstRegex = {
302   DT_REGEX,
303   "regex",
304   regex_string_set,
305   regex_string_get,
306   regex_native_set,
307   regex_native_get,
308   NULL, // string_plus_equals
309   NULL, // string_minus_equals
310   regex_reset,
311   regex_destroy,
312 };
313