NeoMutt  2025-12-11-977-g8a21d8
Teaching an old dog new tricks
DOXYGEN
Loading...
Searching...
No Matches
regex.c
Go to the documentation of this file.
1
27
33
34#include "config.h"
35#include <regex.h>
36#include <stdbool.h>
37#include <stdint.h>
38#include <stdlib.h>
39#include "config/types.h"
40#include "atoi.h"
41#include "buffer.h"
42#include "ctype2.h"
43#include "logging2.h"
44#include "mbyte.h"
45#include "memory.h"
46#include "message.h"
47#include "pool.h"
48#include "queue.h"
49#include "regex3.h"
50#include "string2.h"
51
59struct Regex *mutt_regex_compile(const char *str, uint16_t flags)
60{
61 if (!str || (*str == '\0'))
62 return NULL;
63 struct Regex *rx = MUTT_MEM_CALLOC(1, struct Regex);
64 rx->pattern = mutt_str_dup(str);
65 rx->regex = MUTT_MEM_CALLOC(1, regex_t);
66 if (REG_COMP(rx->regex, str, flags) != 0)
67 mutt_regex_free(&rx);
68
69 return rx;
70}
71
80struct Regex *mutt_regex_new(const char *str, uint32_t flags, struct Buffer *err)
81{
82 if (!str || (*str == '\0'))
83 return NULL;
84
85 uint16_t rflags = 0;
86 struct Regex *reg = MUTT_MEM_CALLOC(1, struct Regex);
87
88 reg->regex = MUTT_MEM_CALLOC(1, regex_t);
89 reg->pattern = mutt_str_dup(str);
90
91 /* Should we use smart case matching? */
92 if (((flags & D_REGEX_MATCH_CASE) == 0) && mutt_mb_is_lower(str))
93 rflags |= REG_ICASE;
94
95 /* Is a prefix of '!' allowed? */
96 if (((flags & D_REGEX_ALLOW_NOT) != 0) && (str[0] == '!'))
97 {
98 reg->pat_not = true;
99 str++;
100 }
101
102 int rc = REG_COMP(reg->regex, str, rflags);
103 if (rc != 0)
104 {
105 if (err)
106 regerror(rc, reg->regex, err->data, err->dsize);
107 mutt_regex_free(&reg);
108 return NULL;
109 }
110
111 return reg;
112}
113
118void mutt_regex_free(struct Regex **ptr)
119{
120 if (!ptr || !*ptr)
121 return;
122
123 struct Regex *rx = *ptr;
124 FREE(&rx->pattern);
125 if (rx->regex)
126 regfree(rx->regex);
127 FREE(&rx->regex);
128 FREE(ptr);
129}
130
140int mutt_regexlist_add(struct RegexList *rl, const char *str, uint16_t flags,
141 struct Buffer *err)
142{
143 if (!rl || !str || (*str == '\0'))
144 return 0;
145
146 struct Regex *rx = mutt_regex_compile(str, flags);
147 if (!rx)
148 {
149 buf_printf(err, "Bad regex: %s\n", str);
150 return -1;
151 }
152
153 /* check to make sure the item is not already on this rl */
154 struct RegexNode *np = NULL;
155 STAILQ_FOREACH(np, rl, entries)
156 {
157 if (mutt_istr_equal(rx->pattern, np->regex->pattern))
158 break; /* already on the rl */
159 }
160
161 if (np)
162 {
163 mutt_regex_free(&rx);
164 }
165 else
166 {
167 np = mutt_regexlist_new();
168 np->regex = rx;
169 STAILQ_INSERT_TAIL(rl, np, entries);
170 }
171
172 return 0;
173}
174
179void mutt_regexlist_free(struct RegexList *rl)
180{
181 if (!rl)
182 return;
183
184 struct RegexNode *np = NULL;
185 struct RegexNode *tmp = NULL;
186 STAILQ_FOREACH_SAFE(np, rl, entries, tmp)
187 {
188 STAILQ_REMOVE(rl, np, RegexNode, entries);
190 FREE(&np);
191 }
192 STAILQ_INIT(rl);
193}
194
201bool mutt_regexlist_match(struct RegexList *rl, const char *str)
202{
203 if (!rl || !str)
204 return false;
205 struct RegexNode *np = NULL;
206 STAILQ_FOREACH(np, rl, entries)
207 {
208 if (mutt_regex_match(np->regex, str))
209 {
210 mutt_debug(LL_DEBUG5, "%s matches %s\n", str, np->regex->pattern);
211 return true;
212 }
213 }
214
215 return false;
216}
217
223{
224 return MUTT_MEM_CALLOC(1, struct RegexNode);
225}
226
236int mutt_regexlist_remove(struct RegexList *rl, const char *str)
237{
238 if (!rl || !str)
239 return -1;
240
241 if (mutt_str_equal("*", str))
242 {
243 mutt_regexlist_free(rl); /* "unCMD *" means delete all current entries */
244 return 0;
245 }
246
247 int rc = -1;
248 struct RegexNode *np = NULL;
249 struct RegexNode *tmp = NULL;
250 STAILQ_FOREACH_SAFE(np, rl, entries, tmp)
251 {
252 if (mutt_istr_equal(str, np->regex->pattern))
253 {
254 STAILQ_REMOVE(rl, np, RegexNode, entries);
256 FREE(&np);
257 rc = 0;
258 }
259 }
260
261 return rc;
262}
263
273int mutt_replacelist_add(struct ReplaceList *rl, const char *pat,
274 const char *templ, struct Buffer *err)
275{
276 if (!rl || !pat || (*pat == '\0') || !templ)
277 return 0;
278
279 struct Regex *rx = mutt_regex_compile(pat, REG_ICASE);
280 if (!rx)
281 {
282 buf_printf(err, _("Bad regex: %s"), pat);
283 return -1;
284 }
285
286 /* check to make sure the item is not already on this rl */
287 struct Replace *np = NULL;
288 STAILQ_FOREACH(np, rl, entries)
289 {
290 if (mutt_istr_equal(rx->pattern, np->regex->pattern))
291 {
292 /* Already on the rl. Formerly we just skipped this case, but
293 * now we're supporting removals, which means we're supporting
294 * re-adds conceptually. So we probably want this to imply a
295 * removal, then do an add. We can achieve the removal by freeing
296 * the template, and leaving t pointed at the current item. */
297 FREE(&np->templ);
298 break;
299 }
300 }
301
302 /* If np is set, it's pointing into an extant ReplaceList* that we want to
303 * update. Otherwise we want to make a new one to link at the rl's end. */
304 if (np)
305 {
306 mutt_regex_free(&rx);
307 }
308 else
309 {
311 np->regex = rx;
312 rx = NULL;
313 STAILQ_INSERT_TAIL(rl, np, entries);
314 }
315
316 /* Now np is the Replace that we want to modify. It is prepared. */
317 np->templ = mutt_str_dup(templ);
318
319 /* Find highest match number in template string */
320 np->nmatch = 0;
321 for (const char *p = templ; *p;)
322 {
323 if (*p == '%')
324 {
325 int n = 0;
326 const char *end = mutt_str_atoi(++p, &n);
327 if (!end)
328 {
329 // this is not an error, we might have matched %R or %L in subject-regex
330 mutt_debug(LL_DEBUG2, "Invalid match number in replacelist: '%s'\n", p);
331 }
332 if (n > np->nmatch)
333 {
334 np->nmatch = n;
335 }
336 if (end)
337 {
338 p = end;
339 }
340 else
341 {
342 p++;
343 }
344 }
345 else
346 {
347 p++;
348 }
349 }
350
351 if (np->nmatch > np->regex->regex->re_nsub)
352 {
353 if (err)
354 buf_addstr(err, _("Not enough subexpressions for template"));
356 return -1;
357 }
358
359 np->nmatch++; /* match 0 is always the whole expr */
360 return 0;
361}
362
371char *mutt_replacelist_apply(struct ReplaceList *rl, const char *str)
372{
373 if (!rl || !str || (*str == '\0'))
374 return NULL;
375
376 static regmatch_t *pmatch = NULL;
377 static size_t nmatch = 0;
378 char *p = NULL;
379
380 struct Buffer *src = buf_pool_get();
381 struct Buffer *dst = buf_pool_get();
382
383 buf_strcpy(src, str);
384
385 struct Replace *np = NULL;
386 STAILQ_FOREACH(np, rl, entries)
387 {
388 /* If this pattern needs more matches, expand pmatch. */
389 if (np->nmatch > nmatch)
390 {
391 MUTT_MEM_REALLOC(&pmatch, np->nmatch, regmatch_t);
392 nmatch = np->nmatch;
393 }
394
395 if (mutt_regex_capture(np->regex, buf_string(src), np->nmatch, pmatch))
396 {
397 mutt_debug(LL_DEBUG5, "%s matches %s\n", buf_string(src), np->regex->pattern);
398
399 buf_reset(dst);
400 if (np->templ)
401 {
402 for (p = np->templ; *p;)
403 {
404 if (*p == '%')
405 {
406 p++;
407 if (*p == 'L')
408 {
409 p++;
410 buf_addstr_n(dst, buf_string(src), pmatch[0].rm_so);
411 }
412 else if (*p == 'R')
413 {
414 p++;
415 buf_addstr(dst, src->data + pmatch[0].rm_eo);
416 }
417 else
418 {
419 long n = strtoul(p, &p, 10); /* get subst number */
420 if (n < np->nmatch)
421 {
422 buf_addstr_n(dst, src->data + pmatch[n].rm_so,
423 pmatch[n].rm_eo - pmatch[n].rm_so);
424 }
425 while (mutt_isdigit(*p)) /* skip subst token */
426 p++;
427 }
428 }
429 else
430 {
431 buf_addch(dst, *p++);
432 }
433 }
434 }
435
436 buf_strcpy(src, buf_string(dst));
437 mutt_debug(LL_DEBUG5, "subst %s\n", buf_string(dst));
438 }
439 }
440
441 char *result = buf_strdup(src);
442
443 buf_pool_release(&src);
444 buf_pool_release(&dst);
445 return result;
446}
447
452void mutt_replacelist_free(struct ReplaceList *rl)
453{
454 if (!rl)
455 return;
456
457 struct Replace *np = NULL;
458 struct Replace *tmp = NULL;
459 STAILQ_FOREACH_SAFE(np, rl, entries, tmp)
460 {
461 STAILQ_REMOVE(rl, np, Replace, entries);
463 FREE(&np->templ);
464 FREE(&np);
465 }
466}
467
481bool mutt_replacelist_match(struct ReplaceList *rl, char *buf, size_t buflen, const char *str)
482{
483 if (!rl || !buf || !str)
484 return false;
485
486 static regmatch_t *pmatch = NULL;
487 static size_t nmatch = 0;
488 int tlen = 0;
489 char *p = NULL;
490
491 struct Replace *np = NULL;
492 STAILQ_FOREACH(np, rl, entries)
493 {
494 /* If this pattern needs more matches, expand pmatch. */
495 if (np->nmatch > nmatch)
496 {
497 MUTT_MEM_REALLOC(&pmatch, np->nmatch, regmatch_t);
498 nmatch = np->nmatch;
499 }
500
501 /* Does this pattern match? */
502 if (mutt_regex_capture(np->regex, str, (size_t) np->nmatch, pmatch))
503 {
504 mutt_debug(LL_DEBUG5, "%s matches %s\n", str, np->regex->pattern);
505 mutt_debug(LL_DEBUG5, "%d subs\n", (int) np->regex->regex->re_nsub);
506
507 /* Copy template into buf, with substitutions. */
508 for (p = np->templ; *p && (tlen < (buflen - 1));)
509 {
510 /* backreference to pattern match substring, eg. %1, %2, etc) */
511 if (*p == '%')
512 {
513 char *e = NULL; /* used as pointer to end of integer backreference in strtol() call */
514
515 p++; /* skip over % char */
516 long n = strtol(p, &e, 10);
517 /* Ensure that the integer conversion succeeded (e!=p) and bounds check. The upper bound check
518 * should not strictly be necessary since add_to_spam_list() finds the largest value, and
519 * the static array above is always large enough based on that value. */
520 if ((e != p) && (n >= 0) && (n < np->nmatch) && (pmatch[n].rm_so != -1))
521 {
522 /* copy as much of the substring match as will fit in the output buffer, saving space for
523 * the terminating nul char */
524 for (int idx = pmatch[n].rm_so;
525 (idx < pmatch[n].rm_eo) && (tlen < (buflen - 1)); idx++)
526 {
527 buf[tlen++] = str[idx];
528 }
529 }
530 p = e; /* skip over the parsed integer */
531 }
532 else
533 {
534 buf[tlen++] = *p++;
535 }
536 }
537 /* tlen should always be less than buflen except when buflen<=0
538 * because the bounds checks in the above code leave room for the
539 * terminal nul char. This should avoid returning an unterminated
540 * string to the caller. When buflen<=0 we make no assumption about
541 * the validity of the buf pointer. */
542 if (tlen < buflen)
543 {
544 buf[tlen] = '\0';
545 mutt_debug(LL_DEBUG5, "\"%s\"\n", buf);
546 }
547 return true;
548 }
549 }
550
551 return false;
552}
553
559{
560 return MUTT_MEM_CALLOC(1, struct Replace);
561}
562
569int mutt_replacelist_remove(struct ReplaceList *rl, const char *pat)
570{
571 if (!rl || !pat)
572 return 0;
573
574 int nremoved = 0;
575 struct Replace *np = NULL;
576 struct Replace *tmp = NULL;
577 STAILQ_FOREACH_SAFE(np, rl, entries, tmp)
578 {
579 if (mutt_str_equal(np->regex->pattern, pat))
580 {
581 STAILQ_REMOVE(rl, np, Replace, entries);
583 FREE(&np->templ);
584 FREE(&np);
585 nremoved++;
586 }
587 }
588
589 return nremoved;
590}
591
601bool mutt_regex_capture(const struct Regex *regex, const char *str,
602 size_t nmatch, regmatch_t matches[])
603{
604 if (!regex || !str || !regex->regex)
605 return false;
606
607 int rc = regexec(regex->regex, str, nmatch, matches, 0);
608 return ((rc == 0) ^ regex->pat_not);
609}
610
618bool mutt_regex_match(const struct Regex *regex, const char *str)
619{
620 return mutt_regex_capture(regex, str, 0, NULL);
621}
const char * mutt_str_atoi(const char *str, int *dst)
Convert ASCII string to an integer.
Definition atoi.c:191
Parse a number in a string.
int buf_printf(struct Buffer *buf, const char *fmt,...)
Format a string overwriting a Buffer.
Definition buffer.c:168
size_t buf_addstr_n(struct Buffer *buf, const char *s, size_t len)
Add a string to a Buffer, expanding it if necessary.
Definition buffer.c:109
void buf_reset(struct Buffer *buf)
Reset an existing Buffer.
Definition buffer.c:89
size_t buf_addch(struct Buffer *buf, char c)
Add a single character to a Buffer.
Definition buffer.c:248
size_t buf_addstr(struct Buffer *buf, const char *s)
Add a string to a Buffer.
Definition buffer.c:233
size_t buf_strcpy(struct Buffer *buf, const char *s)
Copy a string into a Buffer.
Definition buffer.c:401
char * buf_strdup(const struct Buffer *buf)
Copy a Buffer's string.
Definition buffer.c:577
General purpose object for storing and parsing strings.
static const char * buf_string(const struct Buffer *buf)
Convert a buffer to a const char * "string".
Definition buffer.h:96
ctype(3) wrapper functions
bool mutt_isdigit(int arg)
Wrapper for isdigit(3)
Definition ctype.c:66
#define mutt_debug(LEVEL,...)
Definition logging2.h:91
Logging Dispatcher.
@ LL_DEBUG5
Log at debug level 5.
Definition logging2.h:49
@ LL_DEBUG2
Log at debug level 2.
Definition logging2.h:46
bool mutt_mb_is_lower(const char *s)
Does a multi-byte string contain only lowercase characters?
Definition mbyte.c:355
Multi-byte String manipulation functions.
Memory management wrappers.
#define FREE(x)
Free memory and set the pointer to NULL.
Definition memory.h:68
#define MUTT_MEM_CALLOC(n, type)
Definition memory.h:52
#define MUTT_MEM_REALLOC(pptr, n, type)
Definition memory.h:55
Message logging.
#define _(a)
Definition message.h:28
int mutt_replacelist_remove(struct ReplaceList *rl, const char *pat)
Remove a pattern from a list.
Definition regex.c:569
struct Regex * mutt_regex_new(const char *str, uint32_t flags, struct Buffer *err)
Create an Regex from a string.
Definition regex.c:80
struct RegexNode * mutt_regexlist_new(void)
Create a new RegexList.
Definition regex.c:222
struct Regex * mutt_regex_compile(const char *str, uint16_t flags)
Create an Regex from a string.
Definition regex.c:59
void mutt_regexlist_free(struct RegexList *rl)
Free a RegexList object.
Definition regex.c:179
int mutt_regexlist_add(struct RegexList *rl, const char *str, uint16_t flags, struct Buffer *err)
Compile a regex string and add it to a list.
Definition regex.c:140
bool mutt_regex_capture(const struct Regex *regex, const char *str, size_t nmatch, regmatch_t matches[])
Match a regex against a string, with provided options.
Definition regex.c:601
void mutt_replacelist_free(struct ReplaceList *rl)
Free a ReplaceList object.
Definition regex.c:452
int mutt_regexlist_remove(struct RegexList *rl, const char *str)
Remove a Regex from a list.
Definition regex.c:236
bool mutt_replacelist_match(struct ReplaceList *rl, char *buf, size_t buflen, const char *str)
Does a string match a pattern?
Definition regex.c:481
char * mutt_replacelist_apply(struct ReplaceList *rl, const char *str)
Apply replacements to a buffer.
Definition regex.c:371
struct Replace * mutt_replacelist_new(void)
Create a new ReplaceList.
Definition regex.c:558
int mutt_replacelist_add(struct ReplaceList *rl, const char *pat, const char *templ, struct Buffer *err)
Add a pattern and a template to a list.
Definition regex.c:273
bool mutt_regexlist_match(struct RegexList *rl, const char *str)
Does a string match any Regex in the list?
Definition regex.c:201
void mutt_regex_free(struct Regex **ptr)
Free a Regex object.
Definition regex.c:118
bool mutt_regex_match(const struct Regex *regex, const char *str)
Shorthand to mutt_regex_capture()
Definition regex.c:618
bool mutt_istr_equal(const char *a, const char *b)
Compare two strings, ignoring case.
Definition string.c:678
char * mutt_str_dup(const char *str)
Copy a string, safely.
Definition string.c:257
bool mutt_str_equal(const char *a, const char *b)
Compare two strings.
Definition string.c:666
struct Buffer * buf_pool_get(void)
Get a Buffer from the pool.
Definition pool.c:91
void buf_pool_release(struct Buffer **ptr)
Return a Buffer to the pool.
Definition pool.c:111
A global pool of Buffers.
#define STAILQ_REMOVE(head, elm, type, field)
Definition queue.h:441
#define STAILQ_INIT(head)
Definition queue.h:410
#define STAILQ_FOREACH(var, head, field)
Definition queue.h:390
#define STAILQ_INSERT_TAIL(head, elm, field)
Definition queue.h:427
#define STAILQ_FOREACH_SAFE(var, head, field, tvar)
Definition queue.h:400
Manage regular expressions.
#define REG_COMP(preg, regex, cflags)
Compile a regular expression.
Definition regex3.h:49
String manipulation functions.
String manipulation buffer.
Definition buffer.h:36
size_t dsize
Length of data.
Definition buffer.h:39
char * data
Pointer to data.
Definition buffer.h:37
List of regular expressions.
Definition regex3.h:95
struct Regex * regex
Regex containing a regular expression.
Definition regex3.h:96
Cached regular expression.
Definition regex3.h:85
char * pattern
printable version
Definition regex3.h:86
bool pat_not
do not match
Definition regex3.h:88
regex_t * regex
compiled expression
Definition regex3.h:87
List of regular expressions.
Definition regex3.h:105
char * templ
Template to match.
Definition regex3.h:108
size_t nmatch
Match the 'nth' occurrence (0 means the whole expression)
Definition regex3.h:107
struct Regex * regex
Regex containing a regular expression.
Definition regex3.h:106
Constants for all the config types.
#define D_REGEX_ALLOW_NOT
Regex can begin with '!'.
Definition types.h:107
#define D_REGEX_MATCH_CASE
Case-sensitive matching.
Definition types.h:106