summaryrefslogtreecommitdiff
path: root/src/sp_config_scanner.re
diff options
context:
space:
mode:
Diffstat (limited to 'src/sp_config_scanner.re')
-rw-r--r--src/sp_config_scanner.re325
1 files changed, 325 insertions, 0 deletions
diff --git a/src/sp_config_scanner.re b/src/sp_config_scanner.re
new file mode 100644
index 0000000..d7c9884
--- /dev/null
+++ b/src/sp_config_scanner.re
@@ -0,0 +1,325 @@
1#include "php_snuffleupagus.h"
2
3/*!types:re2c*/
4
5#define cs_log_error(fmt, ...) sp_log_err("config", fmt, ##__VA_ARGS__)
6#define cs_log_info(fmt, ...) sp_log_msg("config", SP_LOG_INFO, fmt, ##__VA_ARGS__)
7#define cs_log_warning(fmt, ...) sp_log_warn("config", fmt, ##__VA_ARGS__)
8
9
10zend_string *sp_get_arg_string(sp_parsed_keyword *kw) {
11 if (!kw || !kw->arg) {
12 return NULL;
13 }
14 zend_string *ret = zend_string_init(kw->arg, kw->arglen, 1);
15 char *pin, *pout;
16 pin = pout = ZSTR_VAL(ret);
17 char *pend = pin + ZSTR_LEN(ret);
18
19 while (pin < pend) {
20 if (*pin == '\\') {
21 pin++;
22 }
23 *pout = *pin;
24 pin++; pout++;
25 }
26
27 if (pin != pout) {
28 size_t len = pout - ZSTR_VAL(ret);
29 ret = zend_string_truncate(ret, len, 1);
30 ZSTR_VAL(ret)[len] = 0;
31 }
32
33 return ret;
34}
35
36zend_string *sp_get_textual_representation(sp_parsed_keyword *parsed_rule) {
37 // a rule is "sp.keyword...keyword(arg);\0"
38 size_t len = 3; // sp + ;
39 sp_parsed_keyword *kw;
40 for (kw = parsed_rule; kw->kw; kw++) {
41 len++; // .
42 len += kw->kwlen;
43 if (kw->argtype == SP_ARGTYPE_EMPTY) {
44 len += 2; // ()
45 } else if (kw->argtype == SP_ARGTYPE_STR) {
46 len += 4;
47 len += kw->arglen;
48 }
49 }
50 zend_string *ret = zend_string_alloc(len, 1);
51 char *ptr = ZSTR_VAL(ret);
52 memcpy(ptr, "sp", 2); ptr += 2;
53 for (kw = parsed_rule; kw->kw; kw++) {
54 *ptr++ = '.';
55 memcpy(ptr, kw->kw, kw->kwlen); ptr += kw->kwlen;
56 if (kw->argtype == SP_ARGTYPE_EMPTY || kw->argtype == SP_ARGTYPE_STR || kw->argtype == SP_ARGTYPE_UNKNOWN) {
57 *ptr++ = '(';
58 }
59 if (kw->argtype == SP_ARGTYPE_STR && kw->arg) {
60 *ptr++ = '"';
61 memcpy(ptr, kw->arg, kw->arglen); ptr += kw->arglen;
62 *ptr++ = '"';
63 }
64 if (kw->argtype == SP_ARGTYPE_EMPTY || kw->argtype == SP_ARGTYPE_STR || kw->argtype == SP_ARGTYPE_UNKNOWN) {
65 *ptr++ = ')';
66 }
67 }
68 *ptr++ = ';';
69 *ptr = 0;
70 return ret;
71}
72
73static void str_dtor(zval *zv) {
74 zend_string_release_ex(Z_STR_P(zv), 1);
75}
76
77// sy_ functions and macros are helpers for the shunting yard algorithm
78#define sy_res_push(val) \
79 if (cond_res_i >= 100) { cs_log_error("condition too complex on line %d", lineno); goto out; } \
80 cond_res[cond_res_i++] = val;
81#define sy_res_pop() cond_res[--cond_res_i]
82#define sy_op_push(op) \
83 if (cond_op_i >= 100) { cs_log_error("condition too complex on line %d", lineno); goto out; } \
84 cond_op[cond_op_i++] = op;
85#define sy_op_pop() cond_op[--cond_op_i]
86#define sy_op_peek() cond_op[cond_op_i-1]
87
88static inline int sy_op_precedence(char op) {
89 switch (op) {
90 case '!': return 120;
91 case '<':
92 case '>':
93 case 'L': // <=
94 case 'G': // >=
95 return 90;
96 case '&': return 70;
97 case '|': return 60;
98 case '=': return 20;
99 }
100 return 0;
101}
102static inline int sy_op_is_left_assoc(char op) {
103 switch (op) {
104 case '!': return 0;
105 }
106 return 1;
107}
108static int sy_apply_op(char op, int a, int b) {
109 switch (op) {
110 case '!': return !a;
111 case '&': return (b && a);
112 case '|': return (b || a);
113 case '<': return (b < a);
114 case 'L': return (b <= a);
115 case 'G': return (b >= a);
116 case '>': return (b > a);
117 case '=': return (b == a);
118 }
119 return 0;
120}
121
122#define SY_APPLY_OP_FROM_STACK() \
123 char op = sy_op_pop(); \
124 int unary = (op == '!'); \
125 if (cond_res_i < (2 - unary)) { cs_log_error("not enough input on line %d", lineno); goto out; } \
126 int a = sy_res_pop(); \
127 int b = unary ? 0 : sy_res_pop(); \
128 int res = sy_apply_op(op, a, b); \
129 sy_res_push(res);
130
131#define TMPSTR(tmpstr, t2, t1) \
132 char tmpstr[1024]; \
133 size_t tmplen = MIN(t2-t1-2, 1023); \
134 strncpy(tmpstr, t1+1, tmplen); \
135 tmpstr[tmplen] = 0;
136
137
138zend_result sp_config_scan(char *data, zend_result (*process_rule)(sp_parsed_keyword*))
139{
140 const char *YYCURSOR = data;
141 const char *YYMARKER, *t1, *t2, *t3, *t4;
142 /*!stags:re2c format = 'const char *@@;\n'; */
143
144 int ret = FAILURE;
145
146 const int max_keywords = 16;
147 sp_parsed_keyword parsed_rule[max_keywords+1];
148 int kw_i = 0;
149
150 HashTable vars;
151 zend_hash_init(&vars, 10, NULL, str_dtor, 1);
152 zend_hash_str_add_ptr(&vars, ZEND_STRL("PHP_VERSION_ID"), zend_string_init(ZEND_STRL(ZEND_TOSTR(PHP_VERSION_ID)), 1));
153
154
155 int cond_res[100] = {1};
156 int cond_res_i = 0;
157 char cond_op[100] = {0};
158 int cond_op_i = 0;
159
160 int cond = yycinit;
161 long lineno = 1;
162
163 /*!re2c
164 re2c:define:YYCTYPE = "unsigned char";
165 // re2c:define:YYCURSOR = data;
166 re2c:yyfill:enable = 0;
167 re2c:flags:tags = 1;
168 re2c:api:style = free-form;
169 re2c:define:YYGETCONDITION = "cond";
170 re2c:define:YYSETCONDITION = "cond = @@;";
171
172 end = "\x00";
173 nl = "\r"?"\n";
174 ws = [ \t];
175 keyword = [a-zA-Z_][a-zA-Z0-9_]*;
176 string = "\"" ("\\\"" | [^"\r\n])* "\"";
177
178 <init> * { cs_log_error("Parser error on line %d", lineno); goto out; }
179 <init> ws+ { goto yyc_init; }
180 <init> [;#] .* { goto yyc_init; }
181 <init> nl { lineno++; goto yyc_init; }
182 <init> "sp" { kw_i = 0; goto yyc_rule; }
183 <init> end { ret = SUCCESS; goto out; }
184 <init> "@"? "set" ws+ @t1 keyword @t2 ws+ @t3 string @t4 ws* ";"? {
185 if (!cond_res[0]) { goto yyc_init; }
186 char *key = (char*)t1;
187 int keylen = t2-t1;
188 zend_string *tmp = zend_hash_str_find_ptr(&vars, key, keylen);
189 if (tmp) {
190 zend_hash_str_del(&vars, key, keylen);
191 }
192 tmp = zend_string_init(t3+1, t4-t3-2, 1);
193 zend_hash_str_add_ptr(&vars, key, keylen, tmp);
194 goto yyc_init;
195 }
196 <init> "@condition" ws+ { cond_res_i = 0; goto yyc_cond; }
197 <init> "@end_condition" ws* ";" { cond_res[0] = 1; cond_res_i = 0; goto yyc_init; }
198 <init> ( "@log" | "@info" ) ws+ @t1 string @t2 ";"? {
199 if (!cond_res[0]) { goto yyc_init; }
200 TMPSTR(tmpstr, t2, t1);
201 cs_log_info("[line %d]: %s", lineno, tmpstr);
202 goto yyc_init;
203 }
204 <init> ( "@warn" | "@warning" ) ws+ @t1 string @t2 ";"? {
205 if (!cond_res[0]) { goto yyc_init; }
206 TMPSTR(tmpstr, t2, t1);
207 cs_log_warning("[line %d]: %s", lineno, tmpstr);
208 goto yyc_init;
209 }
210 <init> ( "@err" | "@error" ) ws+ @t1 string @t2 ";"? {
211 if (!cond_res[0]) { goto yyc_init; }
212 TMPSTR(tmpstr, t2, t1);
213 cs_log_error("[line %d]: %s", lineno, tmpstr);
214 goto out;
215 }
216
217
218 <cond> ws+ { goto yyc_cond; }
219 <cond> nl { lineno++; goto yyc_cond; }
220 <cond> @t1 keyword @t2 "(" @t3 string? @t4 ")" {
221 if (t4-t3 >= 2 && strlen("extension_loaded") == t2-t1 && strncmp("extension_loaded", t1, t2-t1) == 0) {
222 int is_loaded = (zend_hash_str_find_ptr(&module_registry, t3+1, t4-t3-2) != NULL);
223 sy_res_push(is_loaded);
224 } else {
225 cs_log_error("unknown function in condition on line %d", lineno);
226 goto out;
227 }
228 goto yyc_cond_op;
229 }
230 <cond> @t1 keyword @t2 {
231 zend_string *tmp = zend_hash_str_find_ptr(&vars, t1, t2-t1);
232 if (!tmp) {
233 cs_log_error("unknown variable in condition on line %d", lineno);
234 goto out;
235 }
236 sy_res_push(atoi(ZSTR_VAL(tmp)));
237 goto yyc_cond_op;
238 }
239 <cond> @t1 [0-9]+ @t2 { sy_res_push(atoi(t1)); goto yyc_cond_op; }
240 <cond> @t1 "!" { sy_op_push(*t1); goto yyc_cond; }
241 <cond> @t1 "(" { sy_op_push(*t1); goto yyc_cond; }
242 <cond_op> ws+ { goto yyc_cond_op; }
243 <cond_op> nl { lineno++; goto yyc_cond_op; }
244 <cond_op> @t1 ( "&&" | "||" | "<" | ">" | "==" | "<=" | ">=") @t2 {
245 char op1 = *t1;
246 if (t2-t1 == 2) {
247 switch (op1) {
248 case '<': op1 = 'L'; break; // <=
249 case '>': op1 = 'G'; break; // >=
250 }
251 }
252 while (cond_op_i && sy_op_peek() != '(' && ((sy_op_precedence(sy_op_peek()) > sy_op_precedence(*t1)) || (sy_op_precedence(sy_op_peek()) == sy_op_precedence(*t1) && sy_op_is_left_assoc(*t1)))) {
253 SY_APPLY_OP_FROM_STACK();
254 }
255 sy_op_push(*t1);
256 goto yyc_cond;
257 }
258 <cond_op> ")" {
259 while (cond_op_i && sy_op_peek() != '(') {
260 SY_APPLY_OP_FROM_STACK();
261 }
262 if (cond_op_i == 0 || sy_op_peek() != '(') {
263 cs_log_error("unbalanced parathesis on line %d", lineno); goto out;
264 }
265 cond_op_i--;
266 goto yyc_cond_op;
267 }
268 <cond_op> ";" {
269 while (cond_op_i) {
270 if (sy_op_peek() == '(') { cs_log_error("unbalanced parathesis on line %d", lineno); goto out; }
271 SY_APPLY_OP_FROM_STACK();
272 }
273 if (cond_res_i > 1) { cs_log_error("invalid condition on line %d", lineno); goto out; }
274 goto yyc_init;
275 }
276 <cond, cond_op> * { cs_log_error("Syntax error in condition on line %d", lineno); goto out; }
277
278 <rule> ws+ { goto yyc_rule; }
279 <rule> nl / ( nl | ws )* "." { lineno++; goto yyc_rule; }
280 <rule> "." @t1 keyword @t2 ( "(" @t3 ( string? | keyword ) @t4 ")" )? {
281 if (!cond_res[0]) { goto yyc_rule; }
282 if (kw_i == max_keywords) {
283 cs_log_error("Too many keywords in rule (more than %d) on line %d", max_keywords, lineno);
284 goto out;
285 }
286 sp_parsed_keyword kw = {.kw = (char*)t1, .kwlen = t2-t1, .arg = (char*)t3, .arglen = t4-t3, .argtype = SP_ARGTYPE_UNKNOWN, .lineno = lineno};
287 if (t3 && t4) {
288 if (t3 == t4) {
289 kw.argtype = SP_ARGTYPE_EMPTY;
290 } else if (t4-t3 >= 2 && *t3 == '"') {
291 kw.arg = (char*)t3 + 1;
292 kw.arglen = t4 - t3 - 2;
293 kw.argtype = SP_ARGTYPE_STR;
294 } else {
295 zend_string *tmp = zend_hash_str_find_ptr(&vars, t3, t4-t3);
296 if (!tmp) {
297 cs_log_error("unknown variable on line %d", lineno);
298 goto out;
299 }
300 kw.arg = ZSTR_VAL(tmp);
301 kw.arglen = ZSTR_LEN(tmp);
302 kw.argtype = SP_ARGTYPE_STR;
303 }
304 } else {
305 kw.argtype = SP_ARGTYPE_NONE;
306 }
307 parsed_rule[kw_i++] = kw;
308 goto yyc_rule;
309 }
310 <rule> ";" {
311 end_of_rule:
312 if (!cond_res[0]) { goto yyc_init; }
313 parsed_rule[kw_i++] = (sp_parsed_keyword){0, 0, 0, 0, 0, 0};
314 if (process_rule && process_rule(parsed_rule) != SUCCESS) {
315 goto out;
316 }
317 goto yyc_init;
318 }
319 <rule> * { goto end_of_rule; }
320
321 */
322out:
323 zend_hash_destroy(&vars);
324 return ret;
325} \ No newline at end of file