1// Common/Wildcard.cpp
2
3#include "StdAfx.h"
4
5#include "Wildcard.h"
6
7bool g_CaseSensitive =
8  #ifdef _WIN32
9    false;
10  #else
11    true;
12  #endif
13
14
15bool IsPath1PrefixedByPath2(const wchar_t *s1, const wchar_t *s2)
16{
17  if (g_CaseSensitive)
18  {
19    for (;;)
20    {
21      wchar_t c2 = *s2++; if (c2 == 0) return true;
22      wchar_t c1 = *s1++;
23      if (MyCharUpper(c1) !=
24          MyCharUpper(c2))
25        return false;
26    }
27  }
28
29  for (;;)
30  {
31    wchar_t c2 = *s2++; if (c2 == 0) return true;
32    wchar_t c1 = *s1++; if (c1 != c2) return false;
33  }
34}
35
36int CompareFileNames(const wchar_t *s1, const wchar_t *s2) STRING_UNICODE_THROW
37{
38  if (g_CaseSensitive)
39    return wcscmp(s1, s2);
40  return MyStringCompareNoCase(s1, s2);
41}
42
43#ifndef USE_UNICODE_FSTRING
44int CompareFileNames(const char *s1, const char *s2)
45{
46  if (g_CaseSensitive)
47    return wcscmp(fs2us(s1), fs2us(s2));
48  return MyStringCompareNoCase(fs2us(s1), fs2us(s2));
49}
50#endif
51
52// -----------------------------------------
53// this function compares name with mask
54// ? - any char
55// * - any char or empty
56
57static bool EnhancedMaskTest(const wchar_t *mask, const wchar_t *name)
58{
59  for (;;)
60  {
61    wchar_t m = *mask;
62    wchar_t c = *name;
63    if (m == 0)
64      return (c == 0);
65    if (m == '*')
66    {
67      if (EnhancedMaskTest(mask + 1, name))
68        return true;
69      if (c == 0)
70        return false;
71    }
72    else
73    {
74      if (m == '?')
75      {
76        if (c == 0)
77          return false;
78      }
79      else if (m != c)
80        if (g_CaseSensitive || MyCharUpper(m) != MyCharUpper(c))
81          return false;
82      mask++;
83    }
84    name++;
85  }
86}
87
88// --------------------------------------------------
89// Splits path to strings
90
91void SplitPathToParts(const UString &path, UStringVector &pathParts)
92{
93  pathParts.Clear();
94  unsigned len = path.Len();
95  if (len == 0)
96    return;
97  UString name;
98  unsigned prev = 0;
99  for (unsigned i = 0; i < len; i++)
100    if (IsCharDirLimiter(path[i]))
101    {
102      name.SetFrom(path.Ptr(prev), i - prev);
103      pathParts.Add(name);
104      prev = i + 1;
105    }
106  name.SetFrom(path.Ptr(prev), len - prev);
107  pathParts.Add(name);
108}
109
110void SplitPathToParts_2(const UString &path, UString &dirPrefix, UString &name)
111{
112  const wchar_t *start = path;
113  const wchar_t *p = start + path.Len();
114  for (; p != start; p--)
115    if (IsCharDirLimiter(*(p - 1)))
116      break;
117  dirPrefix.SetFrom(path, (unsigned)(p - start));
118  name = p;
119}
120
121void SplitPathToParts_Smart(const UString &path, UString &dirPrefix, UString &name)
122{
123  const wchar_t *start = path;
124  const wchar_t *p = start + path.Len();
125  if (p != start)
126  {
127    if (IsCharDirLimiter(*(p - 1)))
128      p--;
129    for (; p != start; p--)
130      if (IsCharDirLimiter(*(p - 1)))
131        break;
132  }
133  dirPrefix.SetFrom(path, (unsigned)(p - start));
134  name = p;
135}
136
137UString ExtractDirPrefixFromPath(const UString &path)
138{
139  const wchar_t *start = path;
140  const wchar_t *p = start + path.Len();
141  for (; p != start; p--)
142    if (IsCharDirLimiter(*(p - 1)))
143      break;
144  return path.Left((unsigned)(p - start));
145}
146
147UString ExtractFileNameFromPath(const UString &path)
148{
149  const wchar_t *start = path;
150  const wchar_t *p = start + path.Len();
151  for (; p != start; p--)
152    if (IsCharDirLimiter(*(p - 1)))
153      break;
154  return p;
155}
156
157
158bool DoesWildcardMatchName(const UString &mask, const UString &name)
159{
160  return EnhancedMaskTest(mask, name);
161}
162
163bool DoesNameContainWildcard(const UString &path)
164{
165  for (unsigned i = 0; i < path.Len(); i++)
166  {
167    wchar_t c = path[i];
168    if (c == '*' || c == '?')
169      return true;
170  }
171  return false;
172}
173
174
175// ----------------------------------------------------------'
176// NWildcard
177
178namespace NWildcard {
179
180
181#ifdef _WIN32
182bool IsDriveColonName(const wchar_t *s)
183{
184  wchar_t c = s[0];
185  return c != 0 && s[1] == ':' && s[2] == 0 && (c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z');
186}
187#endif
188
189/*
190
191M = MaskParts.Size();
192N = TestNameParts.Size();
193
194                           File                          Dir
195ForFile     rec   M<=N  [N-M, N)                          -
196!ForDir  nonrec   M=N   [0, M)                            -
197
198ForDir      rec   M<N   [0, M) ... [N-M-1, N-1)  same as ForBoth-File
199!ForFile nonrec         [0, M)                   same as ForBoth-File
200
201ForFile     rec   m<=N  [0, M) ... [N-M, N)      same as ForBoth-File
202ForDir   nonrec         [0, M)                   same as ForBoth-File
203
204*/
205
206bool CItem::AreAllAllowed() const
207{
208  return ForFile && ForDir && WildcardMatching && PathParts.Size() == 1 && PathParts.Front() == L"*";
209}
210
211bool CItem::CheckPath(const UStringVector &pathParts, bool isFile) const
212{
213  if (!isFile && !ForDir)
214    return false;
215  int delta = (int)pathParts.Size() - (int)PathParts.Size();
216  if (delta < 0)
217    return false;
218  int start = 0;
219  int finish = 0;
220
221  if (isFile)
222  {
223    if (!ForDir)
224    {
225      if (Recursive)
226        start = delta;
227      else if (delta !=0)
228        return false;
229    }
230    if (!ForFile && delta == 0)
231      return false;
232  }
233
234  if (Recursive)
235  {
236    finish = delta;
237    if (isFile && !ForFile)
238      finish = delta - 1;
239  }
240
241  for (int d = start; d <= finish; d++)
242  {
243    unsigned i;
244    for (i = 0; i < PathParts.Size(); i++)
245    {
246      if (WildcardMatching)
247      {
248        if (!DoesWildcardMatchName(PathParts[i], pathParts[i + d]))
249          break;
250      }
251      else
252      {
253        if (CompareFileNames(PathParts[i], pathParts[i + d]) != 0)
254          break;
255      }
256    }
257    if (i == PathParts.Size())
258      return true;
259  }
260  return false;
261}
262
263bool CCensorNode::AreAllAllowed() const
264{
265  if (!Name.IsEmpty() ||
266      !SubNodes.IsEmpty() ||
267      !ExcludeItems.IsEmpty() ||
268      IncludeItems.Size() != 1)
269    return false;
270  return IncludeItems.Front().AreAllAllowed();
271}
272
273int CCensorNode::FindSubNode(const UString &name) const
274{
275  FOR_VECTOR (i, SubNodes)
276    if (CompareFileNames(SubNodes[i].Name, name) == 0)
277      return i;
278  return -1;
279}
280
281void CCensorNode::AddItemSimple(bool include, CItem &item)
282{
283  if (include)
284    IncludeItems.Add(item);
285  else
286    ExcludeItems.Add(item);
287}
288
289void CCensorNode::AddItem(bool include, CItem &item)
290{
291  if (item.PathParts.Size() <= 1)
292  {
293    if (item.PathParts.Size() != 0 && item.WildcardMatching)
294    {
295      if (!DoesNameContainWildcard(item.PathParts.Front()))
296        item.WildcardMatching = false;
297    }
298    AddItemSimple(include, item);
299    return;
300  }
301  const UString &front = item.PathParts.Front();
302
303  // We can't ignore wildcard, since we don't allow wildcard in SubNodes[].Name
304  // if (item.Wildcard)
305  if (DoesNameContainWildcard(front))
306  {
307    AddItemSimple(include, item);
308    return;
309  }
310  int index = FindSubNode(front);
311  if (index < 0)
312    index = SubNodes.Add(CCensorNode(front, this));
313  item.PathParts.Delete(0);
314  SubNodes[index].AddItem(include, item);
315}
316
317void CCensorNode::AddItem(bool include, const UString &path, bool recursive, bool forFile, bool forDir, bool wildcardMatching)
318{
319  CItem item;
320  SplitPathToParts(path, item.PathParts);
321  item.Recursive = recursive;
322  item.ForFile = forFile;
323  item.ForDir = forDir;
324  item.WildcardMatching = wildcardMatching;
325  AddItem(include, item);
326}
327
328bool CCensorNode::NeedCheckSubDirs() const
329{
330  FOR_VECTOR (i, IncludeItems)
331  {
332    const CItem &item = IncludeItems[i];
333    if (item.Recursive || item.PathParts.Size() > 1)
334      return true;
335  }
336  return false;
337}
338
339bool CCensorNode::AreThereIncludeItems() const
340{
341  if (IncludeItems.Size() > 0)
342    return true;
343  FOR_VECTOR (i, SubNodes)
344    if (SubNodes[i].AreThereIncludeItems())
345      return true;
346  return false;
347}
348
349bool CCensorNode::CheckPathCurrent(bool include, const UStringVector &pathParts, bool isFile) const
350{
351  const CObjectVector<CItem> &items = include ? IncludeItems : ExcludeItems;
352  FOR_VECTOR (i, items)
353    if (items[i].CheckPath(pathParts, isFile))
354      return true;
355  return false;
356}
357
358bool CCensorNode::CheckPathVect(const UStringVector &pathParts, bool isFile, bool &include) const
359{
360  if (CheckPathCurrent(false, pathParts, isFile))
361  {
362    include = false;
363    return true;
364  }
365  include = true;
366  bool finded = CheckPathCurrent(true, pathParts, isFile);
367  if (pathParts.Size() <= 1)
368    return finded;
369  int index = FindSubNode(pathParts.Front());
370  if (index >= 0)
371  {
372    UStringVector pathParts2 = pathParts;
373    pathParts2.Delete(0);
374    if (SubNodes[index].CheckPathVect(pathParts2, isFile, include))
375      return true;
376  }
377  return finded;
378}
379
380bool CCensorNode::CheckPath2(bool isAltStream, const UString &path, bool isFile, bool &include) const
381{
382  UStringVector pathParts;
383  SplitPathToParts(path, pathParts);
384  if (CheckPathVect(pathParts, isFile, include))
385  {
386    if (!include || !isAltStream)
387      return true;
388  }
389  if (isAltStream && !pathParts.IsEmpty())
390  {
391    UString &back = pathParts.Back();
392    int pos = back.Find(L':');
393    if (pos > 0)
394    {
395      back.DeleteFrom(pos);
396      return CheckPathVect(pathParts, isFile, include);
397    }
398  }
399  return false;
400}
401
402bool CCensorNode::CheckPath(bool isAltStream, const UString &path, bool isFile) const
403{
404  bool include;
405  if (CheckPath2(isAltStream, path, isFile, include))
406    return include;
407  return false;
408}
409
410bool CCensorNode::CheckPathToRoot(bool include, UStringVector &pathParts, bool isFile) const
411{
412  if (CheckPathCurrent(include, pathParts, isFile))
413    return true;
414  if (Parent == 0)
415    return false;
416  pathParts.Insert(0, Name);
417  return Parent->CheckPathToRoot(include, pathParts, isFile);
418}
419
420/*
421bool CCensorNode::CheckPathToRoot(bool include, const UString &path, bool isFile) const
422{
423  UStringVector pathParts;
424  SplitPathToParts(path, pathParts);
425  return CheckPathToRoot(include, pathParts, isFile);
426}
427*/
428
429void CCensorNode::AddItem2(bool include, const UString &path, bool recursive, bool wildcardMatching)
430{
431  if (path.IsEmpty())
432    return;
433  bool forFile = true;
434  bool forFolder = true;
435  UString path2 = path;
436  if (IsCharDirLimiter(path.Back()))
437  {
438    path2.DeleteBack();
439    forFile = false;
440  }
441  AddItem(include, path2, recursive, forFile, forFolder, wildcardMatching);
442}
443
444void CCensorNode::ExtendExclude(const CCensorNode &fromNodes)
445{
446  ExcludeItems += fromNodes.ExcludeItems;
447  FOR_VECTOR (i, fromNodes.SubNodes)
448  {
449    const CCensorNode &node = fromNodes.SubNodes[i];
450    int subNodeIndex = FindSubNode(node.Name);
451    if (subNodeIndex < 0)
452      subNodeIndex = SubNodes.Add(CCensorNode(node.Name, this));
453    SubNodes[subNodeIndex].ExtendExclude(node);
454  }
455}
456
457int CCensor::FindPrefix(const UString &prefix) const
458{
459  FOR_VECTOR (i, Pairs)
460    if (CompareFileNames(Pairs[i].Prefix, prefix) == 0)
461      return i;
462  return -1;
463}
464
465void CCensor::AddItem(ECensorPathMode pathMode, bool include, const UString &path, bool recursive, bool wildcardMatching)
466{
467  UStringVector pathParts;
468  if (path.IsEmpty())
469    throw "Empty file path";
470  SplitPathToParts(path, pathParts);
471  bool forFile = true;
472  if (pathParts.Back().IsEmpty())
473  {
474    forFile = false;
475    pathParts.DeleteBack();
476  }
477
478  UString prefix;
479
480  if (pathMode != k_AbsPath)
481  {
482    const UString &front = pathParts.Front();
483    bool isAbs = false;
484
485    if (front.IsEmpty())
486      isAbs = true;
487    else
488    {
489      #ifdef _WIN32
490
491      if (IsDriveColonName(front))
492        isAbs = true;
493      else
494
495      #endif
496
497        FOR_VECTOR (i, pathParts)
498        {
499          const UString &part = pathParts[i];
500          if (part == L".." || part == L".")
501          {
502            isAbs = true;
503            break;
504          }
505        }
506    }
507
508    unsigned numAbsParts = 0;
509    if (isAbs)
510      if (pathParts.Size() > 1)
511        numAbsParts = pathParts.Size() - 1;
512      else
513        numAbsParts = 1;
514
515    #ifdef _WIN32
516
517    // \\?\ case
518    if (numAbsParts >= 3)
519    {
520      if (pathParts[0].IsEmpty() &&
521          pathParts[1].IsEmpty() &&
522          pathParts[2] == L"?")
523      {
524        prefix =
525            WSTRING_PATH_SEPARATOR
526            WSTRING_PATH_SEPARATOR L"?"
527            WSTRING_PATH_SEPARATOR;
528        numAbsParts -= 3;
529        pathParts.DeleteFrontal(3);
530      }
531    }
532
533    #endif
534
535    if (numAbsParts > 1 && pathMode == k_FullPath)
536      numAbsParts = 1;
537
538    // We can't ignore wildcard, since we don't allow wildcard in SubNodes[].Name
539    // if (wildcardMatching)
540    for (unsigned i = 0; i < numAbsParts; i++)
541    {
542      {
543        const UString &front = pathParts.Front();
544        if (DoesNameContainWildcard(front))
545          break;
546        prefix += front;
547        prefix += WCHAR_PATH_SEPARATOR;
548      }
549      pathParts.Delete(0);
550    }
551  }
552
553  int index = FindPrefix(prefix);
554  if (index < 0)
555    index = Pairs.Add(CPair(prefix));
556
557  CItem item;
558  item.PathParts = pathParts;
559  item.ForDir = true;
560  item.ForFile = forFile;
561  item.Recursive = recursive;
562  item.WildcardMatching = wildcardMatching;
563  Pairs[index].Head.AddItem(include, item);
564}
565
566bool CCensor::CheckPath(bool isAltStream, const UString &path, bool isFile) const
567{
568  bool finded = false;
569  FOR_VECTOR (i, Pairs)
570  {
571    bool include;
572    if (Pairs[i].Head.CheckPath2(isAltStream, path, isFile, include))
573    {
574      if (!include)
575        return false;
576      finded = true;
577    }
578  }
579  return finded;
580}
581
582void CCensor::ExtendExclude()
583{
584  unsigned i;
585  for (i = 0; i < Pairs.Size(); i++)
586    if (Pairs[i].Prefix.IsEmpty())
587      break;
588  if (i == Pairs.Size())
589    return;
590  unsigned index = i;
591  for (i = 0; i < Pairs.Size(); i++)
592    if (index != i)
593      Pairs[i].Head.ExtendExclude(Pairs[index].Head);
594}
595
596void CCensor::AddPathsToCensor(ECensorPathMode censorPathMode)
597{
598  FOR_VECTOR(i, CensorPaths)
599  {
600    const CCensorPath &cp = CensorPaths[i];
601    AddItem(censorPathMode, cp.Include, cp.Path, cp.Recursive, cp.WildcardMatching);
602  }
603  CensorPaths.Clear();
604}
605
606void CCensor::AddPreItem(bool include, const UString &path, bool recursive, bool wildcardMatching)
607{
608  CCensorPath &cp = CensorPaths.AddNew();
609  cp.Path = path;
610  cp.Include = include;
611  cp.Recursive = recursive;
612  cp.WildcardMatching = wildcardMatching;
613}
614
615}
616