Fix a corner case of superset matching

The algorithm we're using gets stuck if it has a ? and can only see a *
to feed to it, even if it could skip over that * and consume a character
following it. Remedy this by rearranging the input so * always precedes
? in runs of wildcards, so when we're matching ? we know we can skip
things.
This commit is contained in:
Ed Kellett 2020-10-30 01:06:07 +00:00
parent bc6e29e3b0
commit 57fbf05388
2 changed files with 54 additions and 2 deletions

View file

@ -104,6 +104,30 @@ int match(const char *mask, const char *name)
}
}
void
match_arrange_stars(char *mask)
{
char *swap = NULL;
for (char *p = mask; *p != '\0'; p++)
{
switch (*p)
{
case '*':
if (swap == NULL) break;
*swap++ = '*';
*p = '?';
break;
case '?':
if (swap == NULL) swap = p;
break;
default:
swap = NULL;
break;
}
}
}
/** Check a mask against a mask.
* This test checks using traditional IRC wildcards only: '*' means
* match zero or more characters of any type; '?' means match exactly
@ -115,15 +139,19 @@ int match(const char *mask, const char *name)
* @param[in] name New wildcard-containing mask.
* @return 1 if \a name is equal to or more specific than \a mask, 0 otherwise.
*/
int mask_match(const char *mask, const char *name)
int mask_match(const char *mask_, const char *name)
{
static char mask[BUFSIZE];
const char *m = mask, *n = name;
const char *m_tmp = mask, *n_tmp = name;
int star_p;
s_assert(mask != NULL);
s_assert(mask_ != NULL);
s_assert(name != NULL);
rb_strlcpy(mask, mask_, sizeof mask);
match_arrange_stars(mask);
for (;;)
{
switch (*m)
@ -146,6 +174,7 @@ int mask_match(const char *mask, const char *name)
else if (*m == '?')
{
/* changed for mask_match() */
while (star_p && *n == '*') n++;
if (*n == '*' || !*n)
goto backtrack;
n++;

View file

@ -30,6 +30,8 @@
struct Client me;
void match_arrange_stars(char *);
static void test_match(void)
{
is_int(0, match("*foo*", "bar"), MSG);
@ -40,6 +42,7 @@ static void test_match(void)
static void test_mask_match(void)
{
is_int(0, mask_match("*foo*", "bar"), MSG);
is_int(1, mask_match("*foo*", "foo"), MSG);
@ -63,12 +66,32 @@ static void test_mask_match(void)
is_int(0, mask_match("??", "aaa"), MSG);
}
static void test_arrange_stars(void)
{
{
char rearrange[] = "quick brown fox";
match_arrange_stars(rearrange);
is_string("quick brown fox", rearrange, MSG);
}
{
char rearrange[] = "?*?*?*";
match_arrange_stars(rearrange);
is_string("***???", rearrange, MSG);
}
{
char rearrange[] = "?*? *?*";
match_arrange_stars(rearrange);
is_string("*?? **?", rearrange, MSG);
}
}
int main(int argc, char *argv[])
{
plan_lazy();
test_match();
test_mask_match();
test_arrange_stars();
return 0;
}