diff options
Diffstat (limited to 'src/sp_config_scanner.re')
| -rw-r--r-- | src/sp_config_scanner.re | 325 |
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 | |||
| 10 | zend_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 | |||
| 36 | zend_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 | |||
| 73 | static 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 | |||
| 88 | static 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 | } | ||
| 102 | static inline int sy_op_is_left_assoc(char op) { | ||
| 103 | switch (op) { | ||
| 104 | case '!': return 0; | ||
| 105 | } | ||
| 106 | return 1; | ||
| 107 | } | ||
| 108 | static 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 | |||
| 138 | zend_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 | */ | ||
| 322 | out: | ||
| 323 | zend_hash_destroy(&vars); | ||
| 324 | return ret; | ||
| 325 | } \ No newline at end of file | ||
