// Common/Wildcard.cpp | |
#include "StdAfx.h" | |
#include "Wildcard.h" | |
bool g_CaseSensitive = | |
#ifdef _WIN32 | |
false; | |
#else | |
true; | |
#endif | |
bool IsPath1PrefixedByPath2(const wchar_t *s1, const wchar_t *s2) | |
{ | |
if (g_CaseSensitive) | |
{ | |
for (;;) | |
{ | |
wchar_t c2 = *s2++; if (c2 == 0) return true; | |
wchar_t c1 = *s1++; | |
if (MyCharUpper(c1) != | |
MyCharUpper(c2)) | |
return false; | |
} | |
} | |
for (;;) | |
{ | |
wchar_t c2 = *s2++; if (c2 == 0) return true; | |
wchar_t c1 = *s1++; if (c1 != c2) return false; | |
} | |
} | |
int CompareFileNames(const wchar_t *s1, const wchar_t *s2) STRING_UNICODE_THROW | |
{ | |
if (g_CaseSensitive) | |
return wcscmp(s1, s2); | |
return MyStringCompareNoCase(s1, s2); | |
} | |
#ifndef USE_UNICODE_FSTRING | |
int CompareFileNames(const char *s1, const char *s2) | |
{ | |
if (g_CaseSensitive) | |
return wcscmp(fs2us(s1), fs2us(s2)); | |
return MyStringCompareNoCase(fs2us(s1), fs2us(s2)); | |
} | |
#endif | |
// ----------------------------------------- | |
// this function compares name with mask | |
// ? - any char | |
// * - any char or empty | |
static bool EnhancedMaskTest(const wchar_t *mask, const wchar_t *name) | |
{ | |
for (;;) | |
{ | |
wchar_t m = *mask; | |
wchar_t c = *name; | |
if (m == 0) | |
return (c == 0); | |
if (m == '*') | |
{ | |
if (EnhancedMaskTest(mask + 1, name)) | |
return true; | |
if (c == 0) | |
return false; | |
} | |
else | |
{ | |
if (m == '?') | |
{ | |
if (c == 0) | |
return false; | |
} | |
else if (m != c) | |
if (g_CaseSensitive || MyCharUpper(m) != MyCharUpper(c)) | |
return false; | |
mask++; | |
} | |
name++; | |
} | |
} | |
// -------------------------------------------------- | |
// Splits path to strings | |
void SplitPathToParts(const UString &path, UStringVector &pathParts) | |
{ | |
pathParts.Clear(); | |
unsigned len = path.Len(); | |
if (len == 0) | |
return; | |
UString name; | |
unsigned prev = 0; | |
for (unsigned i = 0; i < len; i++) | |
if (IsCharDirLimiter(path[i])) | |
{ | |
name.SetFrom(path.Ptr(prev), i - prev); | |
pathParts.Add(name); | |
prev = i + 1; | |
} | |
name.SetFrom(path.Ptr(prev), len - prev); | |
pathParts.Add(name); | |
} | |
void SplitPathToParts_2(const UString &path, UString &dirPrefix, UString &name) | |
{ | |
const wchar_t *start = path; | |
const wchar_t *p = start + path.Len(); | |
for (; p != start; p--) | |
if (IsCharDirLimiter(*(p - 1))) | |
break; | |
dirPrefix.SetFrom(path, (unsigned)(p - start)); | |
name = p; | |
} | |
void SplitPathToParts_Smart(const UString &path, UString &dirPrefix, UString &name) | |
{ | |
const wchar_t *start = path; | |
const wchar_t *p = start + path.Len(); | |
if (p != start) | |
{ | |
if (IsCharDirLimiter(*(p - 1))) | |
p--; | |
for (; p != start; p--) | |
if (IsCharDirLimiter(*(p - 1))) | |
break; | |
} | |
dirPrefix.SetFrom(path, (unsigned)(p - start)); | |
name = p; | |
} | |
UString ExtractDirPrefixFromPath(const UString &path) | |
{ | |
const wchar_t *start = path; | |
const wchar_t *p = start + path.Len(); | |
for (; p != start; p--) | |
if (IsCharDirLimiter(*(p - 1))) | |
break; | |
return path.Left((unsigned)(p - start)); | |
} | |
UString ExtractFileNameFromPath(const UString &path) | |
{ | |
const wchar_t *start = path; | |
const wchar_t *p = start + path.Len(); | |
for (; p != start; p--) | |
if (IsCharDirLimiter(*(p - 1))) | |
break; | |
return p; | |
} | |
bool DoesWildcardMatchName(const UString &mask, const UString &name) | |
{ | |
return EnhancedMaskTest(mask, name); | |
} | |
bool DoesNameContainWildcard(const UString &path) | |
{ | |
for (unsigned i = 0; i < path.Len(); i++) | |
{ | |
wchar_t c = path[i]; | |
if (c == '*' || c == '?') | |
return true; | |
} | |
return false; | |
} | |
// ----------------------------------------------------------' | |
// NWildcard | |
namespace NWildcard { | |
#ifdef _WIN32 | |
bool IsDriveColonName(const wchar_t *s) | |
{ | |
wchar_t c = s[0]; | |
return c != 0 && s[1] == ':' && s[2] == 0 && (c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z'); | |
} | |
#endif | |
/* | |
M = MaskParts.Size(); | |
N = TestNameParts.Size(); | |
File Dir | |
ForFile rec M<=N [N-M, N) - | |
!ForDir nonrec M=N [0, M) - | |
ForDir rec M<N [0, M) ... [N-M-1, N-1) same as ForBoth-File | |
!ForFile nonrec [0, M) same as ForBoth-File | |
ForFile rec m<=N [0, M) ... [N-M, N) same as ForBoth-File | |
ForDir nonrec [0, M) same as ForBoth-File | |
*/ | |
bool CItem::AreAllAllowed() const | |
{ | |
return ForFile && ForDir && WildcardMatching && PathParts.Size() == 1 && PathParts.Front() == L"*"; | |
} | |
bool CItem::CheckPath(const UStringVector &pathParts, bool isFile) const | |
{ | |
if (!isFile && !ForDir) | |
return false; | |
int delta = (int)pathParts.Size() - (int)PathParts.Size(); | |
if (delta < 0) | |
return false; | |
int start = 0; | |
int finish = 0; | |
if (isFile) | |
{ | |
if (!ForDir) | |
{ | |
if (Recursive) | |
start = delta; | |
else if (delta !=0) | |
return false; | |
} | |
if (!ForFile && delta == 0) | |
return false; | |
} | |
if (Recursive) | |
{ | |
finish = delta; | |
if (isFile && !ForFile) | |
finish = delta - 1; | |
} | |
for (int d = start; d <= finish; d++) | |
{ | |
unsigned i; | |
for (i = 0; i < PathParts.Size(); i++) | |
{ | |
if (WildcardMatching) | |
{ | |
if (!DoesWildcardMatchName(PathParts[i], pathParts[i + d])) | |
break; | |
} | |
else | |
{ | |
if (CompareFileNames(PathParts[i], pathParts[i + d]) != 0) | |
break; | |
} | |
} | |
if (i == PathParts.Size()) | |
return true; | |
} | |
return false; | |
} | |
bool CCensorNode::AreAllAllowed() const | |
{ | |
if (!Name.IsEmpty() || | |
!SubNodes.IsEmpty() || | |
!ExcludeItems.IsEmpty() || | |
IncludeItems.Size() != 1) | |
return false; | |
return IncludeItems.Front().AreAllAllowed(); | |
} | |
int CCensorNode::FindSubNode(const UString &name) const | |
{ | |
FOR_VECTOR (i, SubNodes) | |
if (CompareFileNames(SubNodes[i].Name, name) == 0) | |
return i; | |
return -1; | |
} | |
void CCensorNode::AddItemSimple(bool include, CItem &item) | |
{ | |
if (include) | |
IncludeItems.Add(item); | |
else | |
ExcludeItems.Add(item); | |
} | |
void CCensorNode::AddItem(bool include, CItem &item) | |
{ | |
if (item.PathParts.Size() <= 1) | |
{ | |
if (item.PathParts.Size() != 0 && item.WildcardMatching) | |
{ | |
if (!DoesNameContainWildcard(item.PathParts.Front())) | |
item.WildcardMatching = false; | |
} | |
AddItemSimple(include, item); | |
return; | |
} | |
const UString &front = item.PathParts.Front(); | |
// We can't ignore wildcard, since we don't allow wildcard in SubNodes[].Name | |
// if (item.Wildcard) | |
if (DoesNameContainWildcard(front)) | |
{ | |
AddItemSimple(include, item); | |
return; | |
} | |
int index = FindSubNode(front); | |
if (index < 0) | |
index = SubNodes.Add(CCensorNode(front, this)); | |
item.PathParts.Delete(0); | |
SubNodes[index].AddItem(include, item); | |
} | |
void CCensorNode::AddItem(bool include, const UString &path, bool recursive, bool forFile, bool forDir, bool wildcardMatching) | |
{ | |
CItem item; | |
SplitPathToParts(path, item.PathParts); | |
item.Recursive = recursive; | |
item.ForFile = forFile; | |
item.ForDir = forDir; | |
item.WildcardMatching = wildcardMatching; | |
AddItem(include, item); | |
} | |
bool CCensorNode::NeedCheckSubDirs() const | |
{ | |
FOR_VECTOR (i, IncludeItems) | |
{ | |
const CItem &item = IncludeItems[i]; | |
if (item.Recursive || item.PathParts.Size() > 1) | |
return true; | |
} | |
return false; | |
} | |
bool CCensorNode::AreThereIncludeItems() const | |
{ | |
if (IncludeItems.Size() > 0) | |
return true; | |
FOR_VECTOR (i, SubNodes) | |
if (SubNodes[i].AreThereIncludeItems()) | |
return true; | |
return false; | |
} | |
bool CCensorNode::CheckPathCurrent(bool include, const UStringVector &pathParts, bool isFile) const | |
{ | |
const CObjectVector<CItem> &items = include ? IncludeItems : ExcludeItems; | |
FOR_VECTOR (i, items) | |
if (items[i].CheckPath(pathParts, isFile)) | |
return true; | |
return false; | |
} | |
bool CCensorNode::CheckPathVect(const UStringVector &pathParts, bool isFile, bool &include) const | |
{ | |
if (CheckPathCurrent(false, pathParts, isFile)) | |
{ | |
include = false; | |
return true; | |
} | |
include = true; | |
bool finded = CheckPathCurrent(true, pathParts, isFile); | |
if (pathParts.Size() <= 1) | |
return finded; | |
int index = FindSubNode(pathParts.Front()); | |
if (index >= 0) | |
{ | |
UStringVector pathParts2 = pathParts; | |
pathParts2.Delete(0); | |
if (SubNodes[index].CheckPathVect(pathParts2, isFile, include)) | |
return true; | |
} | |
return finded; | |
} | |
bool CCensorNode::CheckPath2(bool isAltStream, const UString &path, bool isFile, bool &include) const | |
{ | |
UStringVector pathParts; | |
SplitPathToParts(path, pathParts); | |
if (CheckPathVect(pathParts, isFile, include)) | |
{ | |
if (!include || !isAltStream) | |
return true; | |
} | |
if (isAltStream && !pathParts.IsEmpty()) | |
{ | |
UString &back = pathParts.Back(); | |
int pos = back.Find(L':'); | |
if (pos > 0) | |
{ | |
back.DeleteFrom(pos); | |
return CheckPathVect(pathParts, isFile, include); | |
} | |
} | |
return false; | |
} | |
bool CCensorNode::CheckPath(bool isAltStream, const UString &path, bool isFile) const | |
{ | |
bool include; | |
if (CheckPath2(isAltStream, path, isFile, include)) | |
return include; | |
return false; | |
} | |
bool CCensorNode::CheckPathToRoot(bool include, UStringVector &pathParts, bool isFile) const | |
{ | |
if (CheckPathCurrent(include, pathParts, isFile)) | |
return true; | |
if (Parent == 0) | |
return false; | |
pathParts.Insert(0, Name); | |
return Parent->CheckPathToRoot(include, pathParts, isFile); | |
} | |
/* | |
bool CCensorNode::CheckPathToRoot(bool include, const UString &path, bool isFile) const | |
{ | |
UStringVector pathParts; | |
SplitPathToParts(path, pathParts); | |
return CheckPathToRoot(include, pathParts, isFile); | |
} | |
*/ | |
void CCensorNode::AddItem2(bool include, const UString &path, bool recursive, bool wildcardMatching) | |
{ | |
if (path.IsEmpty()) | |
return; | |
bool forFile = true; | |
bool forFolder = true; | |
UString path2 = path; | |
if (IsCharDirLimiter(path.Back())) | |
{ | |
path2.DeleteBack(); | |
forFile = false; | |
} | |
AddItem(include, path2, recursive, forFile, forFolder, wildcardMatching); | |
} | |
void CCensorNode::ExtendExclude(const CCensorNode &fromNodes) | |
{ | |
ExcludeItems += fromNodes.ExcludeItems; | |
FOR_VECTOR (i, fromNodes.SubNodes) | |
{ | |
const CCensorNode &node = fromNodes.SubNodes[i]; | |
int subNodeIndex = FindSubNode(node.Name); | |
if (subNodeIndex < 0) | |
subNodeIndex = SubNodes.Add(CCensorNode(node.Name, this)); | |
SubNodes[subNodeIndex].ExtendExclude(node); | |
} | |
} | |
int CCensor::FindPrefix(const UString &prefix) const | |
{ | |
FOR_VECTOR (i, Pairs) | |
if (CompareFileNames(Pairs[i].Prefix, prefix) == 0) | |
return i; | |
return -1; | |
} | |
void CCensor::AddItem(ECensorPathMode pathMode, bool include, const UString &path, bool recursive, bool wildcardMatching) | |
{ | |
UStringVector pathParts; | |
if (path.IsEmpty()) | |
throw "Empty file path"; | |
SplitPathToParts(path, pathParts); | |
bool forFile = true; | |
if (pathParts.Back().IsEmpty()) | |
{ | |
forFile = false; | |
pathParts.DeleteBack(); | |
} | |
UString prefix; | |
if (pathMode != k_AbsPath) | |
{ | |
const UString &front = pathParts.Front(); | |
bool isAbs = false; | |
if (front.IsEmpty()) | |
isAbs = true; | |
else | |
{ | |
#ifdef _WIN32 | |
if (IsDriveColonName(front)) | |
isAbs = true; | |
else | |
#endif | |
FOR_VECTOR (i, pathParts) | |
{ | |
const UString &part = pathParts[i]; | |
if (part == L".." || part == L".") | |
{ | |
isAbs = true; | |
break; | |
} | |
} | |
} | |
unsigned numAbsParts = 0; | |
if (isAbs) | |
if (pathParts.Size() > 1) | |
numAbsParts = pathParts.Size() - 1; | |
else | |
numAbsParts = 1; | |
#ifdef _WIN32 | |
// \\?\ case | |
if (numAbsParts >= 3) | |
{ | |
if (pathParts[0].IsEmpty() && | |
pathParts[1].IsEmpty() && | |
pathParts[2] == L"?") | |
{ | |
prefix = | |
WSTRING_PATH_SEPARATOR | |
WSTRING_PATH_SEPARATOR L"?" | |
WSTRING_PATH_SEPARATOR; | |
numAbsParts -= 3; | |
pathParts.DeleteFrontal(3); | |
} | |
} | |
#endif | |
if (numAbsParts > 1 && pathMode == k_FullPath) | |
numAbsParts = 1; | |
// We can't ignore wildcard, since we don't allow wildcard in SubNodes[].Name | |
// if (wildcardMatching) | |
for (unsigned i = 0; i < numAbsParts; i++) | |
{ | |
{ | |
const UString &front = pathParts.Front(); | |
if (DoesNameContainWildcard(front)) | |
break; | |
prefix += front; | |
prefix += WCHAR_PATH_SEPARATOR; | |
} | |
pathParts.Delete(0); | |
} | |
} | |
int index = FindPrefix(prefix); | |
if (index < 0) | |
index = Pairs.Add(CPair(prefix)); | |
CItem item; | |
item.PathParts = pathParts; | |
item.ForDir = true; | |
item.ForFile = forFile; | |
item.Recursive = recursive; | |
item.WildcardMatching = wildcardMatching; | |
Pairs[index].Head.AddItem(include, item); | |
} | |
bool CCensor::CheckPath(bool isAltStream, const UString &path, bool isFile) const | |
{ | |
bool finded = false; | |
FOR_VECTOR (i, Pairs) | |
{ | |
bool include; | |
if (Pairs[i].Head.CheckPath2(isAltStream, path, isFile, include)) | |
{ | |
if (!include) | |
return false; | |
finded = true; | |
} | |
} | |
return finded; | |
} | |
void CCensor::ExtendExclude() | |
{ | |
unsigned i; | |
for (i = 0; i < Pairs.Size(); i++) | |
if (Pairs[i].Prefix.IsEmpty()) | |
break; | |
if (i == Pairs.Size()) | |
return; | |
unsigned index = i; | |
for (i = 0; i < Pairs.Size(); i++) | |
if (index != i) | |
Pairs[i].Head.ExtendExclude(Pairs[index].Head); | |
} | |
void CCensor::AddPathsToCensor(ECensorPathMode censorPathMode) | |
{ | |
FOR_VECTOR(i, CensorPaths) | |
{ | |
const CCensorPath &cp = CensorPaths[i]; | |
AddItem(censorPathMode, cp.Include, cp.Path, cp.Recursive, cp.WildcardMatching); | |
} | |
CensorPaths.Clear(); | |
} | |
void CCensor::AddPreItem(bool include, const UString &path, bool recursive, bool wildcardMatching) | |
{ | |
CCensorPath &cp = CensorPaths.AddNew(); | |
cp.Path = path; | |
cp.Include = include; | |
cp.Recursive = recursive; | |
cp.WildcardMatching = wildcardMatching; | |
} | |
} |