Index: pbx.c =================================================================== RCS file: /usr/cvsroot/asterisk/pbx.c,v retrieving revision 1.21 diff -u -b -b -u -r1.21 pbx.c --- pbx.c 22 May 2003 04:38:46 -0000 1.21 +++ pbx.c 13 Aug 2003 07:21:28 -0000 @@ -148,6 +148,7 @@ struct ast_hint *next; }; +int ast_extension_patmatch(const char *pattern, const char *data); static int pbx_builtin_prefix(struct ast_channel *, void *); static int pbx_builtin_suffix(struct ast_channel *, void *); @@ -488,86 +489,357 @@ free(p); } -#define EXTENSION_MATCH_CORE(data,pattern,match) {\ - /* All patterns begin with _ */\ - if (pattern[0] != '_') \ - return 0;\ - /* Start optimistic */\ - match=1;\ - pattern++;\ - while(match && *data && *pattern && (*pattern != '/')) {\ - switch(toupper(*pattern)) {\ - case '[': \ - {\ - int i,border=0;\ - char *where;\ - match=0;\ - pattern++;\ - where=strchr(pattern,']');\ - if (where)\ - border=(int)(where-pattern);\ - if (!where || border > strlen(pattern)) {\ - ast_log(LOG_WARNING, "Wrong usage of [] in the extension\n");\ - return match;\ - }\ - for (i=0; i= pattern[i] && *data <= pattern[i+2]) {\ - res=1;\ - } else {\ - i+=2;\ - continue;\ - }\ - }\ - if (res==1 || *data==pattern[i]) {\ - match = 1;\ - break;\ - }\ - }\ - pattern+=border;\ - break;\ - }\ - case 'N':\ - if ((*data < '2') || (*data > '9'))\ - match=0;\ - break;\ - case 'X':\ - if ((*data < '0') || (*data > '9'))\ - match = 0;\ - break;\ - case 'Z':\ - if ((*data < '1') || (*data > '9'))\ - match = 0;\ - break;\ - case '.':\ - /* Must match */\ - return 1;\ - case ' ':\ - case '-':\ - /* Ignore these characters */\ - data--;\ - break;\ - default:\ - if (*data != *pattern)\ - match =0;\ - }\ - data++;\ - pattern++;\ - }\ +int patmatch_groupcounter = 0; +char patmatch_group[80] = ""; + +/* Derived from code by Steffen Offermann 1991, public domain + http://www.cs.umu.se/~isak/Snippets/xstrcmp.c + + * a regex must start with "_" + * regex patterns are case-insensitive except characters inside [] + * "." matches zero or more characters (as in * in glob) + * character ranges as in [0-9a-zA-Z] + * X,Z,N match 0-9,1-9,2-9 resp. + + new additional features: + * "?" matches any character + * negation as in [^0] ("any char but 0") + or [^a-z] + * explicit quantifiers as in X{2,4} ("from 2 to 4 digits"), + or X{2,} ("at least 2 digits"), + or X{2} ("exactly 2 digits"), + * regex-style quantifiers like ?, + and * are supported by + "{}" grouping. + ? <=> {0,1} + + <=> {1,} + * <=> {0,} + * grouping as in N(1X){1,2} ("one or two sequences of 1X") + * capturing (dependent on AST_PBX_MATCH_CAPTURE) + With () grouped matches are stored in subsequent numbered global + variables, starting with $1, $2 and so on. + * alternation as in (01|0|99) ("01 or 0 or 99") + */ +int ast_extension_patmatch(const char *pattern, char *data) +{ + int i,border=0; + char *where; + static char prev = '\0'; + static char groupdata[80] = ""; + static char *group = patmatch_group; + int groupcounter = patmatch_groupcounter; + + if (option_debug) + ast_log(LOG_DEBUG, " >>> \"%s\" =~ /%s/\n", data, pattern); + switch (toupper(*pattern)) + { + case '\0': + if (option_debug) + ast_log(LOG_DEBUG, " !>>> \"%s\" => %s\n", data, !*data ? "OK" : "FAIL"); + return !*data; + + case ' ': + case '-': + /* Ignore these characters in the pattern */ + return *data && ast_extension_patmatch(pattern+1, data); + + case '.' : /* wildcard as '*' in glob(). Match any sequence of characters. 0 or more */ + prev = *pattern; + if (! *(pattern+1) ) + return 1; /* return *data; => match one or more */ + else + return ast_extension_patmatch(pattern+1, data) || (*data && ast_extension_patmatch(pattern, data+1)); + + /* wildcard character: Match any char */ + case '?' : + prev = *pattern; + return *data && ast_extension_patmatch(pattern+1, data+1); + + case 'X': /* 0-9 */ + prev = *pattern; + return ((*data >= '0') && (*data <= '9')) && ast_extension_patmatch(pattern+1, data+1); + + case 'Z': /* 1-9 */ + prev = *pattern; + return ((*data >= '1') && (*data <= '9')) && ast_extension_patmatch(pattern+1, data+1); + + case 'N': /* 2-9 */ + prev = *pattern; + return ((*data >= '2') && (*data <= '9')) && ast_extension_patmatch(pattern+1, data+1); + + case '{': /* quantifier {n[,m]} */ + { + char *comma; + int cpos; + where=strchr(pattern,'}'); + if (where) { + border=(int)(where-pattern); + comma = strchr(pattern,','); + } + if (!where || border > strlen(pattern)) { + ast_log(LOG_WARNING, "Wrong %s pattern usage\n", pattern); + return 0; + } else { + char tmp[8]; + int from, to; + if (comma) + cpos = (int)(comma-pattern); + else + cpos = border; + strncpy(tmp,pattern+1,cpos-1); + tmp[cpos-1] = '\0'; + from = atoi(tmp); + if (comma) { + if (border-cpos > 1) { /* {f,t} */ + strncpy(tmp,comma+1,border-cpos); + tmp[border-cpos+1] = '\0'; + to = atoi(tmp); + } else { /* {f,} */ + to = strlen(data); /* may fail if after the group are more pattern chars */ + if (*(pattern+border+1)) { + to = to - strlen(pattern+border+1) + 1; + } + } + } else { /* {f} */ + if (from == 0) { + ast_log(LOG_WARNING, "Invalid {0} pattern quantifier %s\n", pattern); + return 0; + } + to = from; + } + if (from < 0 || to <= 0 || to < from) { + ast_log(LOG_WARNING, "Invalid pattern quantifier %s\n", pattern); + return 0; + } + + if (*group) { /* check for repeated pattern{n,m} in previous group */ + int i; + for (i=0; i< strlen(group); i++) { + data--; + } + if (option_debug) + ast_log(LOG_DEBUG, ">>> check for repeated pattern{%d,%d} of group '%s' in data '%s'\n", from, to, group, data); + strcat(group,"."); + } else { + if (option_debug) + ast_log(LOG_DEBUG, ">>> check for repeated pattern{%d,%d} in previous character '%c'\n", from, to, prev); + data--; + group[0] = prev; + group[1] = '.'; + group[2] = '\0'; + } + *tmp = prev; + for (i=to; i>=from; i--) { + if (ast_extension_patmatch_repeated(group,data,i)) break; + } + prev = *tmp; + if (i >= from || !from) { /* if found */ + if (option_debug) + ast_log(LOG_DEBUG, " >>>> found '%s' in data '%s' after %d runs\n", group, data, i); + char name[16]; + data = data + (i * (strlen(group)- 1)) - 1; + int l = strlen(groupdata) - strlen(data); + /* data = data-i+from-1; */ /* possible failure here! */ + if (prev == ')') { /* grouping => capture */ + *(group+strlen(group)-1) = '\0'; + groupdata[l+1] = '\0'; + if (option_debug) + ast_log(LOG_DEBUG, " >>>>> end of group '%s', data: %s\n", group, groupdata); + /* capture the found data in variables $1, $2, ... */ +#ifdef AST_PBX_MATCH_CAPTURE + sprintf(name,"%d",++groupcounter); + pbx_builtin_setvar_helper(NULL,name,groupdata); + if (option_verbose > 2) + ast_log(VERBOSE_PREFIX_3 "global variable $%s set to '%s'\n", name, groupdata); +#endif + } + } + *group = '\0'; + prev = '\0'; + if (i >= from) { /* found: continue */ + if (option_debug) + ast_log(LOG_DEBUG, " >>>> found in round %d from %d\n", i, to); + if (*data) { + if (*(pattern+border+1)) /* if the tail check fails, try the other rounds */ + if (ast_extension_patmatch(pattern+border+1, data+1)) + return 1; + else return (ast_extension_patmatch_repeated(group, data, i--) && + ast_extension_patmatch(pattern+border+1, data+i)); + else + return ast_extension_patmatch(pattern+border+1, data+1); + } + else + return 1; + } else if (from == 0) { /* not found, but special case from=0: no match needed */ + if (option_debug) + ast_log(LOG_DEBUG, " >>>> not found, but no match needed and data exhausted\n"); + if (*data) + return ast_extension_patmatch(pattern+border+1, data+1); + else + return 1; + } else /* not found */ + return 0; + } + } + /* unreachable code */ + + case '(': /* grouping */ + prev = *pattern; + if (*group) { + ast_log(LOG_WARNING, "Unexpected subgroup ( in pattern %s\n", pattern); + return 0; + } + where=strchr(pattern,')'); + if (where) + border=(int)(where-pattern); + if (!where || border > strlen(pattern)) { + ast_log(LOG_WARNING, "Wrong (%s) pattern usage\n", pattern); + return 0; + } + strncpy(group,pattern+1,border-1); + group[border-1] = '\0'; + strcpy(groupdata,data); + if (option_debug) + ast_log(LOG_DEBUG, ">>> group '%s' stored, data: '%s'\n", group, groupdata); + if (strchr(pattern,'|')) { /* alternations */ + char *s, *scopy, *sep, *sepcopy; + s = scopy = (char *) malloc(strlen(pattern)); + sepcopy = (char *) malloc(strlen(pattern)); + strcpy(s,group); + while (sep = strsep(&s,"|")) { + strcpy(sepcopy,sep); + strcat(sepcopy,pattern+border+1); + if (option_debug) + ast_log(LOG_DEBUG, " >>>> alternative '%s' =~ /%s/\n", sepcopy, data); + if (ast_extension_patmatch(sepcopy, data)) break; + if (!*data) { + sep = NULL; break; + } + } + free(scopy); + if (sep) { /* found */ + free(sepcopy); + return 1; + } else { + free(sepcopy); + return 0; + } + } else { + return ast_extension_patmatch(pattern+1, data); + } + + case ')': /* group end */ + prev = *pattern; + if (!*group) { + ast_log(LOG_WARNING, "Unexpected ) in pattern %s\n", pattern); + return 0; + } else { + if (pattern[1] != '{') { /* capture without quantifiers */ + char name[16]; + int l = strlen(groupdata) - strlen(data); + groupdata[l-1] = '\0'; + *(group+strlen(group)-1) = '\0'; + if (option_debug) + ast_log(LOG_DEBUG, ">>> end of group '%s', data: %s\n", group, groupdata); +#ifdef AST_PBX_MATCH_CAPTURE + /* capture the found data in variables $1, $2, ... */ + sprintf(name,"%d",++groupcounter); + pbx_builtin_setvar_helper(NULL,name,groupdata); + ast_log(VERBOSE_PREFIX_3 "global variable $%s set to '%s'\n", name, groupdata); +#endif + *group = '\0'; + } + } + return ast_extension_patmatch(pattern+1, data); + + case '|': /* alternation */ + if (!*group) { + ast_log(LOG_WARNING, "Need group for | in %s\n", pattern); + return 0; + } + + case '[': /* Character ranges: [0-9a-zA-Z] */ + prev = *pattern; + pattern++; + where=strchr(pattern,']'); + if (where) + border=(int)(where-pattern); + if (!where || border > strlen(pattern)) { + ast_log(LOG_WARNING, "Wrong [%s] pattern usage\n", pattern); + return 0; + } + if (*pattern == '^') { /* Negation like [^...] */ + for (i=1; i= pattern[i] && *data <= pattern[i+2]) { + return 0; + } else { + i+=2; + continue; + } + } + } + return ast_extension_patmatch(where+1, data+1); + } else { + for (i=0; i= pattern[i] && *data <= pattern[i+2]) { + return ast_extension_patmatch(where+1, data+1); + } else { + i+=2; + continue; + } + } + } + } + } + break; + + default : + prev = *pattern; + return (toupper(*pattern) == toupper(*data)) && ast_extension_patmatch(pattern+1, data+1); + } + return 0; +} + +/* try exactly num repetitions, from high to from */ +int ast_extension_patmatch_repeated(const char *pattern, char *data, const int num) +{ + int i; + ast_log(LOG_DEBUG, " >>> try %d repetitions of '%s' in data '%s'\n", num, pattern, data); + if (num <= 0) return 0; + for (i=1; i<=num; i++) { + ast_log(LOG_DEBUG, " >>>> round %d with data %s\n", i, data); + if (!ast_extension_patmatch(pattern, data)) return 0; + data = data + strlen(pattern) - 1; + } + return 1; } int ast_extension_match(char *pattern, char *data) { int match; - /* If they're the same return */ - if (!strcmp(pattern, data)) - return 1; - EXTENSION_MATCH_CORE(data,pattern,match); - /* Must be at the end of both */ - if (*data || (*pattern && (*pattern != '/'))) - match = 0; + patmatch_groupcounter = 0; + *patmatch_group = '\0'; + if (!*pattern) { + ast_log(LOG_WARNING, "ast_extension_match: empty pattern\n"); + return 0; + } + if (!*data) { + ast_log(LOG_WARNING, "ast_extension_match: empty data\n"); + return 0; + } + if (pattern[0] != '_') { + match = (strcmp(pattern, data) == 0); + ast_log(LOG_DEBUG, "ast_extension_match %s == /%s/ => %d\n", data, pattern, match); + } else { + match = ast_extension_patmatch(pattern+1,data); + ast_log(LOG_DEBUG, "ast_extension_match %s =~ /%s/ => %d\n", data, pattern+1, match); + } return match; } @@ -583,7 +855,9 @@ (!needmore || (strlen(pattern) > strlen(data)))) { return 1; } - EXTENSION_MATCH_CORE(data,pattern,match); + if (pattern[0] == '_') { + match = ast_extension_patmatch(pattern+1,data); + } /* If there's more or we don't care about more, return non-zero, otlherwise it's a miss */ if (!needmore || *pattern) { return match;