#include "sys_defs.h"
#ifdef HAS_POSIX_REGEXP
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <ctype.h>
#include <regex.h>
#ifdef STRCASECMP_IN_STRINGS_H
#include <strings.h>
#endif
#include "mymalloc.h"
#include "msg.h"
#include "safe.h"
#include "vstream.h"
#include "vstring.h"
#include "stringops.h"
#include "readlline.h"
#include "dict.h"
#include "dict_regexp.h"
#include "mac_parse.h"
#define DICT_REGEXP_OP_MATCH 1
#define DICT_REGEXP_OP_IF 2
#define DICT_REGEXP_OP_ENDIF 3
typedef struct {
char *regexp;
int options;
int match;
} DICT_REGEXP_PATTERN;
typedef struct DICT_REGEXP_RULE {
int op;
int nesting;
int lineno;
struct DICT_REGEXP_RULE *next;
} DICT_REGEXP_RULE;
typedef struct {
DICT_REGEXP_RULE rule;
regex_t *first_exp;
int first_match;
regex_t *second_exp;
int second_match;
char *replacement;
size_t max_sub;
} DICT_REGEXP_MATCH_RULE;
typedef struct {
DICT_REGEXP_RULE rule;
regex_t *expr;
int match;
} DICT_REGEXP_IF_RULE;
typedef struct {
DICT dict;
regmatch_t *pmatch;
DICT_REGEXP_RULE *head;
} DICT_REGEXP;
#define NULL_SUBSTITUTIONS (0)
#define NULL_MATCH_RESULT ((regmatch_t *) 0)
typedef struct {
DICT_REGEXP *dict_regexp;
DICT_REGEXP_MATCH_RULE *match_rule;
const char *lookup_string;
VSTRING *expansion_buf;
} DICT_REGEXP_EXPAND_CONTEXT;
typedef struct {
const char *mapname;
int lineno;
size_t max_sub;
} DICT_REGEXP_PRESCAN_CONTEXT;
#ifndef MAC_PARSE_OK
#define MAC_PARSE_OK 0
#endif
static int dict_regexp_expand(int type, VSTRING *buf, char *ptr)
{
DICT_REGEXP_EXPAND_CONTEXT *ctxt = (DICT_REGEXP_EXPAND_CONTEXT *) ptr;
DICT_REGEXP_MATCH_RULE *match_rule = ctxt->match_rule;
DICT_REGEXP *dict_regexp = ctxt->dict_regexp;
regmatch_t *pmatch;
size_t n;
if (type == MAC_PARSE_VARNAME) {
n = atoi(vstring_str(buf));
if (n < 1 || n > match_rule->max_sub)
msg_panic("regexp map %s, line %d: out of range replacement index \"%s\"",
dict_regexp->dict.name, match_rule->rule.lineno,
vstring_str(buf));
pmatch = dict_regexp->pmatch + n;
if (pmatch->rm_so < 0 || pmatch->rm_so == pmatch->rm_eo)
return (MAC_PARSE_UNDEF);
vstring_strncat(ctxt->expansion_buf,
ctxt->lookup_string + pmatch->rm_so,
pmatch->rm_eo - pmatch->rm_so);
return (MAC_PARSE_OK);
}
else {
vstring_strcat(ctxt->expansion_buf, vstring_str(buf));
return (MAC_PARSE_OK);
}
}
static void dict_regexp_regerror(const char *mapname, int lineno, int error,
const regex_t *expr)
{
char errbuf[256];
(void) regerror(error, expr, errbuf, sizeof(errbuf));
msg_warn("regexp map %s, line %d: %s", mapname, lineno, errbuf);
}
#define DICT_REGEXP_REGEXEC(err, map, line, expr, match, str, nsub, pmatch) \
((err) = regexec((expr), (str), (nsub), (pmatch), 0), \
((err) == REG_NOMATCH ? !(match) : \
(err) == 0 ? (match) : \
(dict_regexp_regerror((map), (line), (err), (expr)), 0)))
static const char *dict_regexp_lookup(DICT *dict, const char *lookup_string)
{
DICT_REGEXP *dict_regexp = (DICT_REGEXP *) dict;
DICT_REGEXP_RULE *rule;
DICT_REGEXP_IF_RULE *if_rule;
DICT_REGEXP_MATCH_RULE *match_rule;
DICT_REGEXP_EXPAND_CONTEXT expand_context;
static VSTRING *expansion_buf;
int error;
int nesting = 0;
dict_errno = 0;
if (msg_verbose)
msg_info("dict_regexp_lookup: %s: %s", dict->name, lookup_string);
for (rule = dict_regexp->head; rule; rule = rule->next) {
if (nesting < rule->nesting)
continue;
switch (rule->op) {
case DICT_REGEXP_OP_MATCH:
match_rule = (DICT_REGEXP_MATCH_RULE *) rule;
if (!DICT_REGEXP_REGEXEC(error, dict->name, rule->lineno,
match_rule->first_exp,
match_rule->first_match,
lookup_string,
match_rule->max_sub > 0 ?
match_rule->max_sub + 1 : 0,
dict_regexp->pmatch))
continue;
if (match_rule->second_exp
&& !DICT_REGEXP_REGEXEC(error, dict->name, rule->lineno,
match_rule->second_exp,
match_rule->second_match,
lookup_string,
NULL_SUBSTITUTIONS,
NULL_MATCH_RESULT))
continue;
if (match_rule->max_sub == 0)
return (match_rule->replacement);
if (!expansion_buf)
expansion_buf = vstring_alloc(10);
VSTRING_RESET(expansion_buf);
expand_context.expansion_buf = expansion_buf;
expand_context.lookup_string = lookup_string;
expand_context.match_rule = match_rule;
expand_context.dict_regexp = dict_regexp;
if (mac_parse(match_rule->replacement, dict_regexp_expand,
(char *) &expand_context) & MAC_PARSE_ERROR)
msg_panic("regexp map %s, line %d: bad replacement syntax",
dict->name, rule->lineno);
VSTRING_TERMINATE(expansion_buf);
return (vstring_str(expansion_buf));
case DICT_REGEXP_OP_IF:
if_rule = (DICT_REGEXP_IF_RULE *) rule;
if (DICT_REGEXP_REGEXEC(error, dict->name, rule->lineno,
if_rule->expr, if_rule->match, lookup_string,
NULL_SUBSTITUTIONS, NULL_MATCH_RESULT))
nesting++;
continue;
case DICT_REGEXP_OP_ENDIF:
nesting--;
continue;
default:
msg_panic("dict_regexp_lookup: impossible operation %d", rule->op);
}
}
return (0);
}
static void dict_regexp_close(DICT *dict)
{
DICT_REGEXP *dict_regexp = (DICT_REGEXP *) dict;
DICT_REGEXP_RULE *rule;
DICT_REGEXP_RULE *next;
DICT_REGEXP_MATCH_RULE *match_rule;
DICT_REGEXP_IF_RULE *if_rule;
for (rule = dict_regexp->head; rule; rule = next) {
next = rule->next;
switch (rule->op) {
case DICT_REGEXP_OP_MATCH:
match_rule = (DICT_REGEXP_MATCH_RULE *) rule;
if (match_rule->first_exp) {
regfree(match_rule->first_exp);
myfree((char *) match_rule->first_exp);
}
if (match_rule->second_exp) {
regfree(match_rule->second_exp);
myfree((char *) match_rule->second_exp);
}
if (match_rule->replacement)
myfree((char *) match_rule->replacement);
break;
case DICT_REGEXP_OP_IF:
if_rule = (DICT_REGEXP_IF_RULE *) rule;
if (if_rule->expr) {
regfree(if_rule->expr);
myfree((char *) if_rule->expr);
}
break;
case DICT_REGEXP_OP_ENDIF:
break;
default:
msg_panic("dict_regexp_close: unknown operation %d", rule->op);
}
myfree((char *) rule);
}
if (dict_regexp->pmatch)
myfree((char *) dict_regexp->pmatch);
dict_free(dict);
}
static int dict_regexp_get_pat(const char *mapname, int lineno, char **bufp,
DICT_REGEXP_PATTERN *pat)
{
char *p = *bufp;
char re_delim;
pat->match = 1;
while (*p == '!') {
pat->match = !pat->match;
p++;
}
while (*p && ISSPACE(*p))
p++;
if (*p == 0) {
msg_warn("regexp map %s, line %d: no regexp: skipping this rule",
mapname, lineno);
return (0);
}
re_delim = *p++;
pat->regexp = p;
while (*p) {
if (*p == '\\') {
if (p[1])
p++;
else
break;
} else if (*p == re_delim) {
break;
}
++p;
}
if (!*p) {
msg_warn("regexp map %s, line %d: no closing regexp delimiter \"%c\": "
"skipping this rule", mapname, lineno, re_delim);
return (0);
}
*p++ = 0;
pat->options = REG_EXTENDED | REG_ICASE;
while (*p && !ISSPACE(*p) && *p != '!') {
switch (*p) {
case 'i':
pat->options ^= REG_ICASE;
break;
case 'm':
pat->options ^= REG_NEWLINE;
break;
case 'x':
pat->options ^= REG_EXTENDED;
break;
default:
msg_warn("regexp map %s, line %d: unknown regexp option \"%c\": "
"skipping this rule", mapname, lineno, *p);
return (0);
}
++p;
}
*bufp = p;
return (1);
}
static int dict_regexp_get_pats(const char *mapname, int lineno, char **p,
DICT_REGEXP_PATTERN *first_pat,
DICT_REGEXP_PATTERN *second_pat)
{
if (dict_regexp_get_pat(mapname, lineno, p, first_pat) == 0)
return (0);
if (**p == '!') {
#if 0
static int bitrot_warned = 0;
if (bitrot_warned == 0) {
msg_warn("regexp file %s, line %d: /pattern1/!/pattern2/ goes away,"
" use \"if !/pattern2/ ... /pattern1/ ... endif\" instead",
mapname, lineno);
bitrot_warned = 1;
}
#endif
if (dict_regexp_get_pat(mapname, lineno, p, second_pat) == 0)
return (0);
} else {
second_pat->regexp = 0;
}
return (1);
}
static int dict_regexp_prescan(int type, VSTRING *buf, char *context)
{
DICT_REGEXP_PRESCAN_CONTEXT *ctxt = (DICT_REGEXP_PRESCAN_CONTEXT *) context;
size_t n;
if (type == MAC_PARSE_VARNAME) {
if (!alldig(vstring_str(buf))) {
msg_warn("regexp map %s, line %d: non-numeric replacement index \"%s\"",
ctxt->mapname, ctxt->lineno, vstring_str(buf));
return (MAC_PARSE_ERROR);
}
n = atoi(vstring_str(buf));
if (n > ctxt->max_sub)
ctxt->max_sub = n;
}
return (MAC_PARSE_OK);
}
static regex_t *dict_regexp_compile_pat(const char *mapname, int lineno,
DICT_REGEXP_PATTERN *pat)
{
int error;
regex_t *expr;
expr = (regex_t *) mymalloc(sizeof(*expr));
error = regcomp(expr, pat->regexp, pat->options);
if (error != 0) {
dict_regexp_regerror(mapname, lineno, error, expr);
myfree((char *) expr);
return (0);
}
return (expr);
}
static DICT_REGEXP_RULE *dict_regexp_rule_alloc(int op, int nesting,
int lineno,
size_t size)
{
DICT_REGEXP_RULE *rule;
rule = (DICT_REGEXP_RULE *) mymalloc(size);
rule->op = op;
rule->nesting = nesting;
rule->lineno = lineno;
rule->next = 0;
return (rule);
}
static DICT_REGEXP_RULE *dict_regexp_parseline(const char *mapname, int lineno,
char *line, int nesting,
int dict_flags)
{
char *p;
p = line;
if (!ISALNUM(*p)) {
DICT_REGEXP_PATTERN first_pat;
DICT_REGEXP_PATTERN second_pat;
DICT_REGEXP_PRESCAN_CONTEXT prescan_context;
regex_t *first_exp;
regex_t *second_exp;
DICT_REGEXP_MATCH_RULE *match_rule;
if (!dict_regexp_get_pats(mapname, lineno, &p, &first_pat, &second_pat))
return (0);
while (*p && ISSPACE(*p))
++p;
if (!*p) {
msg_warn("regexp map %s, line %d: using empty replacement string",
mapname, lineno);
}
prescan_context.mapname = mapname;
prescan_context.lineno = lineno;
prescan_context.max_sub = 0;
if (mac_parse(p, dict_regexp_prescan, (char *) &prescan_context)
& MAC_PARSE_ERROR) {
msg_warn("regexp map %s, line %d: bad replacement syntax: "
"skipping this rule", mapname, lineno);
return (0);
}
#define FREE_EXPR_AND_RETURN(expr, rval) \
{ regfree(expr); myfree((char *) (expr)); return (rval); }
if (prescan_context.max_sub == 0 || first_pat.match == 0) {
first_pat.options |= REG_NOSUB;
} else if (dict_flags & DICT_FLAG_NO_REGSUB) {
msg_warn("regexp map %s, line %d: "
"regular expression substitution is not allowed: "
"skipping this rule", mapname, lineno);
return (0);
}
if ((first_exp = dict_regexp_compile_pat(mapname, lineno,
&first_pat)) == 0)
return (0);
if (prescan_context.max_sub > 0 && first_pat.match == 0) {
msg_warn("regexp map %s, line %d: $number found in negative match replacement text: "
"skipping this rule", mapname, lineno);
FREE_EXPR_AND_RETURN(first_exp, 0);
}
if (prescan_context.max_sub > first_exp->re_nsub) {
msg_warn("regexp map %s, line %d: out of range replacement index \"%d\": "
"skipping this rule", mapname, lineno,
prescan_context.max_sub);
FREE_EXPR_AND_RETURN(first_exp, 0);
}
if (second_pat.regexp != 0) {
second_pat.options |= REG_NOSUB;
if ((second_exp = dict_regexp_compile_pat(mapname, lineno,
&second_pat)) == 0)
FREE_EXPR_AND_RETURN(first_exp, 0);
} else {
second_exp = 0;
}
match_rule = (DICT_REGEXP_MATCH_RULE *)
dict_regexp_rule_alloc(DICT_REGEXP_OP_MATCH, nesting, lineno,
sizeof(DICT_REGEXP_MATCH_RULE));
match_rule->first_exp = first_exp;
match_rule->first_match = first_pat.match;
match_rule->max_sub =
(prescan_context.max_sub > 0 ? prescan_context.max_sub + 1 : 0);
match_rule->second_exp = second_exp;
match_rule->second_match = second_pat.match;
match_rule->replacement = mystrdup(p);
return ((DICT_REGEXP_RULE *) match_rule);
}
else if (strncasecmp(p, "IF", 2) == 0 && !ISALNUM(p[2])) {
DICT_REGEXP_PATTERN pattern;
regex_t *expr;
DICT_REGEXP_IF_RULE *if_rule;
p += 2;
while (*p && ISSPACE(*p))
p++;
if (!dict_regexp_get_pat(mapname, lineno, &p, &pattern))
return (0);
while (*p && ISSPACE(*p))
++p;
if (*p)
msg_warn("regexp map %s, line %d: ignoring extra text after IF",
mapname, lineno);
if ((expr = dict_regexp_compile_pat(mapname, lineno, &pattern)) == 0)
return (0);
if_rule = (DICT_REGEXP_IF_RULE *)
dict_regexp_rule_alloc(DICT_REGEXP_OP_IF, nesting, lineno,
sizeof(DICT_REGEXP_IF_RULE));
if_rule->expr = expr;
if_rule->match = pattern.match;
return ((DICT_REGEXP_RULE *) if_rule);
}
else if (strncasecmp(p, "ENDIF", 5) == 0 && !ISALNUM(p[5])) {
DICT_REGEXP_RULE *rule;
p += 5;
if (nesting == 0) {
msg_warn("regexp map %s, line %d: ignoring ENDIF without matching IF",
mapname, lineno);
return (0);
}
while (*p && ISSPACE(*p))
++p;
if (*p)
msg_warn("regexp map %s, line %d: ignoring extra text after ENDIF",
mapname, lineno);
rule = dict_regexp_rule_alloc(DICT_REGEXP_OP_ENDIF, nesting, lineno,
sizeof(DICT_REGEXP_RULE));
return (rule);
}
else {
msg_warn("regexp map %s, line %d: ignoring unrecognized request",
mapname, lineno);
return (0);
}
}
DICT *dict_regexp_open(const char *mapname, int unused_flags, int dict_flags)
{
DICT_REGEXP *dict_regexp;
VSTREAM *map_fp;
VSTRING *line_buffer;
DICT_REGEXP_RULE *rule;
DICT_REGEXP_RULE *last_rule = 0;
int lineno = 0;
size_t max_sub = 0;
int nesting = 0;
char *p;
line_buffer = vstring_alloc(100);
dict_regexp = (DICT_REGEXP *) dict_alloc(DICT_TYPE_REGEXP, mapname,
sizeof(*dict_regexp));
dict_regexp->dict.lookup = dict_regexp_lookup;
dict_regexp->dict.close = dict_regexp_close;
dict_regexp->dict.flags = dict_flags | DICT_FLAG_PATTERN;
dict_regexp->head = 0;
dict_regexp->pmatch = 0;
if ((map_fp = vstream_fopen(mapname, O_RDONLY, 0)) == 0)
msg_fatal("open %s: %m", mapname);
while (readlline(line_buffer, map_fp, &lineno)) {
p = vstring_str(line_buffer);
trimblanks(p, 0)[0] = 0;
if (*p == 0)
continue;
rule = dict_regexp_parseline(mapname, lineno, p, nesting, dict_flags);
if (rule == 0)
continue;
if (rule->op == DICT_REGEXP_OP_MATCH) {
if (((DICT_REGEXP_MATCH_RULE *) rule)->max_sub > max_sub)
max_sub = ((DICT_REGEXP_MATCH_RULE *) rule)->max_sub;
} else if (rule->op == DICT_REGEXP_OP_IF) {
nesting++;
} else if (rule->op == DICT_REGEXP_OP_ENDIF) {
nesting--;
}
if (last_rule == 0)
dict_regexp->head = rule;
else
last_rule->next = rule;
last_rule = rule;
}
if (nesting)
msg_warn("regexp map %s, line %d: more IFs than ENDIFs",
mapname, lineno);
if (max_sub > 0)
dict_regexp->pmatch =
(regmatch_t *) mymalloc(sizeof(regmatch_t) * (max_sub + 1));
vstring_free(line_buffer);
vstream_fclose(map_fp);
return (DICT_DEBUG (&dict_regexp->dict));
}
#endif