/*
Userspace Virtual Filesystem
Copyright (C) 2015 Sebastian Herbord. All rights reserved.
This file is part of usvfs.
usvfs is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
usvfs is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with usvfs. If not, see .
*/
#include "wildcard.h"
#include "logging.h"
#include "windows_sane.h"
static bool IsInnerMatch(LPCWSTR pszString, LPCWSTR pszMatch)
{
while (*pszMatch != L'\0') {
if ((*pszMatch == L'?') || (*pszMatch == L'>')) {
if (!*pszString) {
// ? must match exactly one character
return false;
}
++pszString;
++pszMatch;
} else if ((*pszMatch == L'*') || (*pszMatch == L'<')) {
if (IsInnerMatch(pszString, pszMatch + 1)) {
// * may match empty string or we may have matched something already
return true;
}
return *pszString && IsInnerMatch(pszString + 1, pszMatch);
// the rest of the string can't be matched
} else {
if (CharUpperW(MAKEINTRESOURCEW(MAKELONG(*pszString++, 0))) !=
CharUpperW(MAKEINTRESOURCEW(MAKELONG(*pszMatch++, 0)))) {
// regular chars compare
return false;
}
}
}
return !*pszString && !*pszMatch;
}
static LPCSTR InnerMatch(LPCSTR pszString, LPCSTR pszMatch)
{
// We have a special case where string is empty ("") and the mask is "*".
// We need to handle this too. So we can't test on !*pszString here.
// The loop breaks when the match string is exhausted.
while (*pszString != '\0') {
// Single wildcard character
if ((*pszMatch == '?') || (*pszMatch == '>')) {
// can't match directory separator
if ((*pszString == '\\') || (*pszString == '/'))
return nullptr;
// Matches any character except empty string
if (*pszString == '\0') {
// string consumed, part of the pattern is left
return pszMatch;
}
// OK next
++pszString;
++pszMatch;
} else if ((*pszMatch == '*') || (*pszMatch == '<')) {
// * doesn't match directory separators
if ((*pszString == '\\') || (*pszString == '/')) {
++pszMatch;
continue;
}
// Need to do some tricks.
// 1. The wildcard * is ignored.
// So just an empty string matches. This is done by recursion.
// Because we eat one character from the match string, the
// recursion will stop.
{
LPCSTR remainder = InnerMatch(pszString, pszMatch + 1);
if (remainder != nullptr) {
// we have a match and the * replaces no other character
return remainder;
}
}
// 2. Chance we eat the next character and try it again, with a
// wildcard * match. This is done by recursion. Because we eat
// one character from the string, the recursion will stop.
if (*pszString != '\0') {
LPCSTR remainder = InnerMatch(pszString + 1, pszMatch);
if (remainder != nullptr) {
return remainder;
}
}
// Nothing worked with this wildcard.
return nullptr;
} else {
// Standard compare of 2 chars. Note that *pszSring might be 0
// here, but then we never get a match on *pszMask that has always
// a value while inside this loop.
if (CharUpperA(MAKEINTRESOURCEA(MAKELONG(*pszString++, 0))) !=
CharUpperA(MAKEINTRESOURCEA(MAKELONG(*pszMatch++, 0))))
return nullptr;
}
}
// successful match if the input string was completely consumed
if (*pszString == '\0') {
while ((*pszMatch == '*') || (*pszMatch == '<')) {
++pszMatch;
}
return pszMatch;
} else {
return nullptr;
}
}
namespace usvfs::shared::wildcard
{
bool Match(LPCWSTR pszString, LPCWSTR pszMatch)
{
if (*pszString == L'.') {
// cmd.exe seems to ignore
return Match(pszString + 1, pszMatch);
} else {
size_t len = wcslen(pszMatch);
if ((len > 2) && (wcscmp(pszMatch + len - 2, L".*") == 0)) {
// cmd.exe seems to completely ignore .* at the end.
std::wstring temp(pszMatch, pszMatch + len - 2);
return IsInnerMatch(pszString, temp.c_str());
}
return IsInnerMatch(pszString, pszMatch);
}
}
bool Match(LPCSTR pszString, LPCSTR pszMatch)
{
if (*pszString == '.') {
// cmd.exe seems to ignore
return Match(pszString + 1, pszMatch);
} else {
size_t len = strlen(pszMatch);
LPCSTR res = nullptr;
if ((len > 2) && (strcmp(pszMatch + len - 2, ".*") == 0)) {
// cmd.exe seems to completely ignore .* at the end.
std::string temp(pszMatch, pszMatch + len - 2);
res = InnerMatch(pszString, temp.c_str());
} else {
res = InnerMatch(pszString, pszMatch);
}
return ((res != nullptr) && (*res == '\0'));
}
}
LPCSTR PartialMatch(LPCSTR pszString, LPCSTR pszMatch)
{
if (*pszString == '.') {
// cmd.exe seems to ignore dots at the start
return PartialMatch(pszString + 1, pszMatch);
} else {
size_t len = strlen(pszMatch);
if ((len > 2) && (strcmp(pszMatch + len - 2, ".*") == 0)) {
// in cmd.exe there seems to be no difference between * and
// *.*
std::string temp(pszMatch, pszMatch + len - 2);
LPCSTR pos = InnerMatch(pszString, temp.c_str());
if (pos != nullptr) {
if (*pos == '\0') {
return pszMatch + strlen(pszMatch);
} else {
return pszMatch + (pos - temp.c_str());
}
}
}
return InnerMatch(pszString, pszMatch);
}
}
} // namespace usvfs::shared::wildcard