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(®);
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