1#include "util.h"
2#include "string.h"
3#include "strfilter.h"
4
5/* Operators */
6static const char *OP_and	= "&";	/* Logical AND */
7static const char *OP_or	= "|";	/* Logical OR */
8static const char *OP_not	= "!";	/* Logical NOT */
9
10#define is_operator(c)	((c) == '|' || (c) == '&' || (c) == '!')
11#define is_separator(c)	(is_operator(c) || (c) == '(' || (c) == ')')
12
13static void strfilter_node__delete(struct strfilter_node *node)
14{
15	if (node) {
16		if (node->p && !is_operator(*node->p))
17			zfree((char **)&node->p);
18		strfilter_node__delete(node->l);
19		strfilter_node__delete(node->r);
20		free(node);
21	}
22}
23
24void strfilter__delete(struct strfilter *filter)
25{
26	if (filter) {
27		strfilter_node__delete(filter->root);
28		free(filter);
29	}
30}
31
32static const char *get_token(const char *s, const char **e)
33{
34	const char *p;
35
36	while (isspace(*s))	/* Skip spaces */
37		s++;
38
39	if (*s == '\0') {
40		p = s;
41		goto end;
42	}
43
44	p = s + 1;
45	if (!is_separator(*s)) {
46		/* End search */
47retry:
48		while (*p && !is_separator(*p) && !isspace(*p))
49			p++;
50		/* Escape and special case: '!' is also used in glob pattern */
51		if (*(p - 1) == '\\' || (*p == '!' && *(p - 1) == '[')) {
52			p++;
53			goto retry;
54		}
55	}
56end:
57	*e = p;
58	return s;
59}
60
61static struct strfilter_node *strfilter_node__alloc(const char *op,
62						    struct strfilter_node *l,
63						    struct strfilter_node *r)
64{
65	struct strfilter_node *node = zalloc(sizeof(*node));
66
67	if (node) {
68		node->p = op;
69		node->l = l;
70		node->r = r;
71	}
72
73	return node;
74}
75
76static struct strfilter_node *strfilter_node__new(const char *s,
77						  const char **ep)
78{
79	struct strfilter_node root, *cur, *last_op;
80	const char *e;
81
82	if (!s)
83		return NULL;
84
85	memset(&root, 0, sizeof(root));
86	last_op = cur = &root;
87
88	s = get_token(s, &e);
89	while (*s != '\0' && *s != ')') {
90		switch (*s) {
91		case '&':	/* Exchg last OP->r with AND */
92			if (!cur->r || !last_op->r)
93				goto error;
94			cur = strfilter_node__alloc(OP_and, last_op->r, NULL);
95			if (!cur)
96				goto nomem;
97			last_op->r = cur;
98			last_op = cur;
99			break;
100		case '|':	/* Exchg the root with OR */
101			if (!cur->r || !root.r)
102				goto error;
103			cur = strfilter_node__alloc(OP_or, root.r, NULL);
104			if (!cur)
105				goto nomem;
106			root.r = cur;
107			last_op = cur;
108			break;
109		case '!':	/* Add NOT as a leaf node */
110			if (cur->r)
111				goto error;
112			cur->r = strfilter_node__alloc(OP_not, NULL, NULL);
113			if (!cur->r)
114				goto nomem;
115			cur = cur->r;
116			break;
117		case '(':	/* Recursively parses inside the parenthesis */
118			if (cur->r)
119				goto error;
120			cur->r = strfilter_node__new(s + 1, &s);
121			if (!s)
122				goto nomem;
123			if (!cur->r || *s != ')')
124				goto error;
125			e = s + 1;
126			break;
127		default:
128			if (cur->r)
129				goto error;
130			cur->r = strfilter_node__alloc(NULL, NULL, NULL);
131			if (!cur->r)
132				goto nomem;
133			cur->r->p = strndup(s, e - s);
134			if (!cur->r->p)
135				goto nomem;
136		}
137		s = get_token(e, &e);
138	}
139	if (!cur->r)
140		goto error;
141	*ep = s;
142	return root.r;
143nomem:
144	s = NULL;
145error:
146	*ep = s;
147	strfilter_node__delete(root.r);
148	return NULL;
149}
150
151/*
152 * Parse filter rule and return new strfilter.
153 * Return NULL if fail, and *ep == NULL if memory allocation failed.
154 */
155struct strfilter *strfilter__new(const char *rules, const char **err)
156{
157	struct strfilter *filter = zalloc(sizeof(*filter));
158	const char *ep = NULL;
159
160	if (filter)
161		filter->root = strfilter_node__new(rules, &ep);
162
163	if (!filter || !filter->root || *ep != '\0') {
164		if (err)
165			*err = ep;
166		strfilter__delete(filter);
167		filter = NULL;
168	}
169
170	return filter;
171}
172
173static bool strfilter_node__compare(struct strfilter_node *node,
174				    const char *str)
175{
176	if (!node || !node->p)
177		return false;
178
179	switch (*node->p) {
180	case '|':	/* OR */
181		return strfilter_node__compare(node->l, str) ||
182			strfilter_node__compare(node->r, str);
183	case '&':	/* AND */
184		return strfilter_node__compare(node->l, str) &&
185			strfilter_node__compare(node->r, str);
186	case '!':	/* NOT */
187		return !strfilter_node__compare(node->r, str);
188	default:
189		return strglobmatch(str, node->p);
190	}
191}
192
193/* Return true if STR matches the filter rules */
194bool strfilter__compare(struct strfilter *filter, const char *str)
195{
196	if (!filter)
197		return false;
198	return strfilter_node__compare(filter->root, str);
199}
200