From 35977dadc935dd7be680f89d109eddbc5d1861ad Mon Sep 17 00:00:00 2001 From: Andrea Mazzoleni Date: Wed, 26 Aug 2026 12:43:17 +0200 Subject: [PATCH] Implement documented wildcard escaping in wnmatch_sub --- cmdline/selftest.c | 18 ++++++++++++++++++ cmdline/str.c | 26 ++++++++++++++++++++++++++ cmdline/str.h | 1 + 3 files changed, 45 insertions(+) diff --git a/cmdline/selftest.c b/cmdline/selftest.c index 028edbbc..28d2c0bc 100644 --- a/cmdline/selftest.c +++ b/cmdline/selftest.c @@ -863,6 +863,24 @@ struct { { "Movies*", "Movies4", 0, 0 }, { "Movies*", "Movies4/file.mkv", 0, 1 }, + /* escaping of wildcard metacharacters */ + { "foo\\*bar", "foo*bar", 0, 0 }, + { "foo\\?bar", "foo?bar", 0, 0 }, + { "foo\\[bar", "foo[bar", 0, 0 }, + { "foo\\]bar", "foo]bar", 0, 0 }, + { "foo\\\\bar", "foo\\bar", 0, 0 }, + { "foo\\*bar", "fooXXXbar", 0, 1 }, + { "foo\\?bar", "fooXbar", 0, 1 }, + { "foo\\[bar", "fooXbar", 0, 1 }, + { "foo\\]bar", "fooXbar", 0, 1 }, + { "foo*bar", "fooXXXbar", 0, 0 }, + { "foo?bar", "fooXbar", 0, 0 }, + { "foo[abc]bar", "fooabar", 0, 0 }, + { "foo\\abar", "foo\\abar", 0, 0 }, + { "foo\\abar", "fooabar", 0, 1 }, + { "foo\\", "foo\\", 0, 0 }, + { "foo\\", "foo", 0, 1 }, + { 0 } }; diff --git a/cmdline/str.c b/cmdline/str.c index 581be73a..cbaafe5d 100644 --- a/cmdline/str.c +++ b/cmdline/str.c @@ -360,12 +360,38 @@ static inline int wnmatch_end(const char* t, int match_sub) } } +static inline int is_wild_escape(char c) +{ + return c == '*' + || c == '?' + || c == '[' + || c == ']' + || c == '\\'; +} + int wnmatch_sub(const char* p, const char* t, int match_sub) { char p1 = 0; /* previous char */ while (*p) { char p0 = *p; switch (p0) { + case '\\' : + /* + * Escape wildcard metacharacters. + * + * '\' is the canonical internal escape character. + * On Windows, pathimport() converts the external '^' + * escape syntax to this form before matching. + */ + if (is_wild_escape(p[1])) + ++p; + + if (*t == 0 || !char_match(*p, *t)) + return 1; + + ++p; + ++t; + break; case '?' : /* ? matches any single character except / */ if (*t == 0 || *t == '/') diff --git a/cmdline/str.h b/cmdline/str.h index da996e02..9f508629 100644 --- a/cmdline/str.h +++ b/cmdline/str.h @@ -127,6 +127,7 @@ unsigned strtou(const char* nptr, char** endptr, int base); * - ** (not near a /) like * * - ##/ reduces to nothing in addition to the normal matching of ** (using # instead of * to not mess the C comment) * - [...] matches character classes with support for ranges and negation lile [!...] or [^...], except / + * - \ escapes *, ?, [, ], and \ * * \return 0 if it matches */