diff --git a/pbx.c b/pbx.c index 437fc658ca71ca83d6f5ab19be568bd09987fcdb..bdd62dd0ea70a13a0691ca2906f8e86acf9a3625 100644 --- a/pbx.c +++ b/pbx.c @@ -560,6 +560,127 @@ static void pbx_destroy(struct ast_pbx *p) free(p); } +/*! + * \brief helper functions to sort extensions and patterns in the desired way, + * so that more specific patterns appear first. + * + * ext_cmp1 compares individual characters (or sets of), returning + * an int where bits 0-7 are the ASCII code of the first char in the set, + * while bit 8-15 are the cardinality of the set minus 1. + * This way more specific patterns (smaller cardinality) appear first. + * Wildcards have a special value, so that we can directly compare them to + * sets by subtracting the two values. In particular: + * 0x000xx one character, xx + * 0x0yyxx yy character set starting with xx + * 0x10000 '.' (one or more of anything) + * 0x20000 '!' (zero or more of anything) + * 0x30000 NUL (end of string) + * 0x40000 error in set. + * The pointer to the string is advanced according to needs. + * NOTES: + * 1. the empty set is equivalent to NUL. + * 2. given that a full set has always 0 as the first element, + * we could encode the special cases as 0xffXX where XX + * is 1, 2, 3, 4 as used above. + */ +static int ext_cmp1(const char **p) +{ + uint32_t chars[8]; + int c, cmin = 0xff, count = 0; + const char *end; + + /* load, sign extend and advance pointer until we find + * a valid character. + */ + while ( (c = *(*p)++) && (c == ' ' || c == '-') ) + ; /* ignore some characters */ + + /* always return unless we have a set of chars */ + switch (c) { + default: /* ordinary character */ + return 0x0000 | (c & 0xff); + + case 'N': /* 2..9 */ + return 0x0700 | '2' ; + + case 'X': /* 0..9 */ + return 0x0900 | '0'; + + case 'Z': /* 1..9 */ + return 0x0800 | '1'; + + case '.': /* wildcard */ + return 0x10000; + + case '!': /* earlymatch */ + return 0x20000; /* less specific than NULL */ + + case '\0': /* empty string */ + *p = NULL; + return 0x30000; + + case '[': /* pattern */ + break; + } + /* locate end of set */ + end = strchr(*p, ']'); + + if (end == NULL) { + ast_log(LOG_WARNING, "Wrong usage of [] in the extension\n"); + return 0x40000; /* XXX make this entry go last... */ + } + + bzero(chars, sizeof(chars)); /* clear all chars in the set */ + for (; *p < end ; (*p)++) { + unsigned char c1, c2; /* first-last char in range */ + c1 = (unsigned char)((*p)[0]); + if (*p + 2 < end && (*p)[1] == '-') { /* this is a range */ + c2 = (unsigned char)((*p)[2]); + *p += 2; /* skip a total of 3 chars */ + } else /* individual character */ + c2 = c1; + if (c1 < cmin) + cmin = c1; + for (; c1 <= c2; c1++) { + uint32_t mask = 1 << (c1 % 32); + if ( (chars[ c1 / 32 ] & mask) == 0) + count += 0x100; + chars[ c1 / 32 ] |= mask; + } + } + (*p)++; + return count == 0 ? 0x30000 : (count | cmin); +} + +/*! + * \brief the full routine to compare extensions in rules. + */ +static int ext_cmp(const char *a, const char *b) +{ + /* make sure non-patterns come first. + * If a is not a pattern, it either comes first or + * we use strcmp to compare the strings. + */ + int ret = 0; + + if (a[0] != '_') + return (b[0] == '_') ? -1 : strcmp(a, b); + + /* Now we know a is a pattern; if b is not, a comes first */ + if (b[0] != '_') + return 1; +#if 0 /* old mode for ext matching */ + return strcmp(a, b); +#endif + /* ok we need full pattern sorting routine */ + while (!ret && a && b) + ret = ext_cmp1(&a) - ext_cmp1(&b); + if (ret == 0) + return 0; + else + return (ret > 0) ? 1 : -1; +} + /*! * When looking up extensions, we can have different requests * identified by the 'action' argument, as follows. @@ -4162,10 +4283,10 @@ int ast_add_extension2(struct ast_context *con, } } while(0) /* - * This is a fairly complex routine. Different extensions are kept - * in order by the extension number. Then, extensions of different - * priorities (same extension) are kept in a list, according to the - * peer pointer. + * Sort extensions (or patterns) according to the rules indicated above. + * These are implemented by the function ext_cmp()). + * All priorities for the same ext/pattern/cid are kept in a list, + * using the 'peer' field as a link field.. */ struct ast_exten *tmp, *e, *el = NULL; int res; @@ -4226,14 +4347,7 @@ int ast_add_extension2(struct ast_context *con, ast_mutex_lock(&con->lock); for (e = con->root; e; el = e, e = e->next) { /* scan the extension list */ - /* XXX should use ext_cmp() to sort patterns correctly */ - /* almost strcmp, but make sure patterns are always last! */ - if (e->exten[0] != '_' && extension[0] == '_') - res = -1; - else if (e->exten[0] == '_' && extension[0] != '_') - res = 1; - else - res= strcmp(e->exten, extension); + res = ext_cmp(e->exten, extension); if (res == 0) { /* extension match, now look at cidmatch */ if (!e->matchcid && !tmp->matchcid) res = 0; @@ -4257,7 +4371,10 @@ int ast_add_extension2(struct ast_context *con, } return ret; } - /* ok found the insertion place, right before 'e' (if any) */ + /* + * not an exact match, this is the first entry with this pattern, + * so insert in the main list right before 'e' (if any) + */ tmp->next = e; if (el) el->next = tmp;