1// Windows/FileName.cpp
2
3#include "StdAfx.h"
4
5#include "FileName.h"
6
7#ifndef _UNICODE
8extern bool g_IsNT;
9#endif
10
11namespace NWindows {
12namespace NFile {
13namespace NName {
14
15#ifndef USE_UNICODE_FSTRING
16void NormalizeDirPathPrefix(FString &dirPath)
17{
18  if (dirPath.IsEmpty())
19    return;
20  if (dirPath.Back() != FCHAR_PATH_SEPARATOR)
21    dirPath += FCHAR_PATH_SEPARATOR;
22}
23#endif
24
25void NormalizeDirPathPrefix(UString &dirPath)
26{
27  if (dirPath.IsEmpty())
28    return;
29  if (dirPath.Back() != WCHAR_PATH_SEPARATOR)
30    dirPath += WCHAR_PATH_SEPARATOR;
31}
32
33
34#ifdef _WIN32
35
36const wchar_t *kSuperPathPrefix = L"\\\\?\\";
37static const wchar_t *kSuperUncPrefix = L"\\\\?\\UNC\\";
38
39#define IS_DEVICE_PATH(s)  ((s)[0] == '\\' && (s)[1] == '\\' && (s)[2] == '.' && (s)[3] == '\\')
40#define IS_SUPER_PREFIX(s) ((s)[0] == '\\' && (s)[1] == '\\' && (s)[2] == '?' && (s)[3] == '\\')
41#define IS_SUPER_OR_DEVICE_PATH(s) ((s)[0] == '\\' && (s)[1] == '\\' && ((s)[2] == '?' || (s)[2] == '.') && (s)[3] == '\\')
42#define IS_LETTER_CHAR(c) ((c) >= 'a' && (c) <= 'z' || (c) >= 'A' && (c) <= 'Z')
43
44#define IS_UNC_WITH_SLASH(s) ( \
45  ((s)[0] == 'U' || (s)[0] == 'u') && \
46  ((s)[1] == 'N' || (s)[1] == 'n') && \
47  ((s)[2] == 'C' || (s)[2] == 'c') && \
48   (s)[3] == '\\')
49
50bool IsDevicePath(CFSTR s) throw()
51{
52  #ifdef UNDER_CE
53
54  s = s;
55  return false;
56  /*
57  // actually we don't know the way to open device file in WinCE.
58  unsigned len = MyStringLen(s);
59  if (len < 5 || len > 5 || memcmp(s, FTEXT("DSK"), 3 * sizeof(FChar)) != 0)
60    return false;
61  if (s[4] != ':')
62    return false;
63  // for reading use SG_REQ sg; if (DeviceIoControl(dsk, IOCTL_DISK_READ));
64  */
65
66  #else
67
68  if (!IS_DEVICE_PATH(s))
69    return false;
70  unsigned len = MyStringLen(s);
71  if (len == 6 && s[5] == ':')
72    return true;
73  if (len < 18 || len > 22 || memcmp(s + kDevicePathPrefixSize, FTEXT("PhysicalDrive"), 13 * sizeof(FChar)) != 0)
74    return false;
75  for (unsigned i = 17; i < len; i++)
76    if (s[i] < '0' || s[i] > '9')
77      return false;
78  return true;
79
80  #endif
81}
82
83bool IsSuperUncPath(CFSTR s) throw() { return (IS_SUPER_PREFIX(s) && IS_UNC_WITH_SLASH(s + kSuperPathPrefixSize)); }
84
85bool IsDrivePath(const wchar_t *s) throw() { return IS_LETTER_CHAR(s[0]) && s[1] == ':' && s[2] == '\\'; }
86bool IsSuperPath(const wchar_t *s) throw() { return IS_SUPER_PREFIX(s); }
87bool IsSuperOrDevicePath(const wchar_t *s) throw() { return IS_SUPER_OR_DEVICE_PATH(s); }
88// bool IsSuperUncPath(const wchar_t *s) { return (IS_SUPER_PREFIX(s) && IS_UNC_WITH_SLASH(s + kSuperPathPrefixSize)); }
89
90#ifndef USE_UNICODE_FSTRING
91bool IsDrivePath(CFSTR s) throw() { return IS_LETTER_CHAR(s[0]) && s[1] == ':' && s[2] == '\\'; }
92bool IsSuperPath(CFSTR s) throw() { return IS_SUPER_PREFIX(s); }
93bool IsSuperOrDevicePath(CFSTR s) throw() { return IS_SUPER_OR_DEVICE_PATH(s); }
94#endif // USE_UNICODE_FSTRING
95
96bool IsAbsolutePath(const wchar_t *s) throw()
97{
98  return s[0] == WCHAR_PATH_SEPARATOR || IsDrivePath(s);
99}
100
101static const unsigned kDrivePrefixSize = 3; /* c:\ */
102
103#ifndef USE_UNICODE_FSTRING
104
105static unsigned GetRootPrefixSize_Of_NetworkPath(CFSTR s) throw()
106{
107  // Network path: we look "server\path\" as root prefix
108  int pos = FindCharPosInString(s, '\\');
109  if (pos < 0)
110    return 0;
111  int pos2 = FindCharPosInString(s + pos + 1, '\\');
112  if (pos2 < 0)
113    return 0;
114  return pos + pos2 + 2;
115}
116
117static unsigned GetRootPrefixSize_Of_SimplePath(CFSTR s) throw()
118{
119  if (IsDrivePath(s))
120    return kDrivePrefixSize;
121  if (s[0] != '\\' || s[1] != '\\')
122    return 0;
123  unsigned size = GetRootPrefixSize_Of_NetworkPath(s + 2);
124  return (size == 0) ? 0 : 2 + size;
125}
126
127static unsigned GetRootPrefixSize_Of_SuperPath(CFSTR s) throw()
128{
129  if (IS_UNC_WITH_SLASH(s + kSuperPathPrefixSize))
130  {
131    unsigned size = GetRootPrefixSize_Of_NetworkPath(s + kSuperUncPathPrefixSize);
132    return (size == 0) ? 0 : kSuperUncPathPrefixSize + size;
133  }
134  // we support \\?\c:\ paths and volume GUID paths \\?\Volume{GUID}\"
135  int pos = FindCharPosInString(s + kSuperPathPrefixSize, FCHAR_PATH_SEPARATOR);
136  if (pos < 0)
137    return 0;
138  return kSuperPathPrefixSize + pos + 1;
139}
140
141unsigned GetRootPrefixSize(CFSTR s) throw()
142{
143  if (IS_DEVICE_PATH(s))
144    return kDevicePathPrefixSize;
145  if (IsSuperPath(s))
146    return GetRootPrefixSize_Of_SuperPath(s);
147  return GetRootPrefixSize_Of_SimplePath(s);
148}
149
150#endif // USE_UNICODE_FSTRING
151
152static unsigned GetRootPrefixSize_Of_NetworkPath(const wchar_t *s) throw()
153{
154  // Network path: we look "server\path\" as root prefix
155  int pos = FindCharPosInString(s, L'\\');
156  if (pos < 0)
157    return 0;
158  int pos2 = FindCharPosInString(s + pos + 1, L'\\');
159  if (pos2 < 0)
160    return 0;
161  return pos + pos2 + 2;
162}
163
164static unsigned GetRootPrefixSize_Of_SimplePath(const wchar_t *s) throw()
165{
166  if (IsDrivePath(s))
167    return kDrivePrefixSize;
168  if (s[0] != '\\' || s[1] != '\\')
169    return 0;
170  unsigned size = GetRootPrefixSize_Of_NetworkPath(s + 2);
171  return (size == 0) ? 0 : 2 + size;
172}
173
174static unsigned GetRootPrefixSize_Of_SuperPath(const wchar_t *s) throw()
175{
176  if (IS_UNC_WITH_SLASH(s + kSuperPathPrefixSize))
177  {
178    unsigned size = GetRootPrefixSize_Of_NetworkPath(s + kSuperUncPathPrefixSize);
179    return (size == 0) ? 0 : kSuperUncPathPrefixSize + size;
180  }
181  // we support \\?\c:\ paths and volume GUID paths \\?\Volume{GUID}\"
182  int pos = FindCharPosInString(s + kSuperPathPrefixSize, L'\\');
183  if (pos < 0)
184    return 0;
185  return kSuperPathPrefixSize + pos + 1;
186}
187
188unsigned GetRootPrefixSize(const wchar_t *s) throw()
189{
190  if (IS_DEVICE_PATH(s))
191    return kDevicePathPrefixSize;
192  if (IsSuperPath(s))
193    return GetRootPrefixSize_Of_SuperPath(s);
194  return GetRootPrefixSize_Of_SimplePath(s);
195}
196
197#else // _WIN32
198
199bool IsAbsolutePath(const wchar_t *s) throw() { return s[0] == WCHAR_PATH_SEPARATOR }
200
201#ifndef USE_UNICODE_FSTRING
202unsigned GetRootPrefixSize(CFSTR s) throw() { return s[0] == CHAR_PATH_SEPRATOR ? 1 : 0; }
203#endif
204unsigned GetRootPrefixSize(const wchar_t *s) throw() { return s[0] == CHAR_PATH_SEPRATOR ? 1 : 0; }
205
206#endif // _WIN32
207
208
209#ifndef UNDER_CE
210
211static bool GetCurDir(UString &path)
212{
213  path.Empty();
214  DWORD needLength;
215  #ifndef _UNICODE
216  if (!g_IsNT)
217  {
218    TCHAR s[MAX_PATH + 2];
219    s[0] = 0;
220    needLength = ::GetCurrentDirectory(MAX_PATH + 1, s);
221    path = fs2us(fas2fs(s));
222  }
223  else
224  #endif
225  {
226    WCHAR s[MAX_PATH + 2];
227    s[0] = 0;
228    needLength = ::GetCurrentDirectoryW(MAX_PATH + 1, s);
229    path = s;
230  }
231  return (needLength > 0 && needLength <= MAX_PATH);
232}
233
234static bool ResolveDotsFolders(UString &s)
235{
236  #ifdef _WIN32
237  s.Replace(L'/', WCHAR_PATH_SEPARATOR);
238  #endif
239  for (int i = 0;;)
240  {
241    wchar_t c = s[i];
242    if (c == 0)
243      return true;
244    if (c == '.' && (i == 0 || s[i - 1] == WCHAR_PATH_SEPARATOR))
245    {
246      wchar_t c1 = s[i + 1];
247      if (c1 == '.')
248      {
249        wchar_t c2 = s[i + 2];
250        if (c2 == WCHAR_PATH_SEPARATOR || c2 == 0)
251        {
252          if (i == 0)
253            return false;
254          int k = i - 2;
255          for (; k >= 0; k--)
256            if (s[k] == WCHAR_PATH_SEPARATOR)
257              break;
258          unsigned num;
259          if (k >= 0)
260          {
261            num = i + 2 - k;
262            i = k;
263          }
264          else
265          {
266            num = (c2 == 0 ? (i + 2) : (i + 3));
267            i = 0;
268          }
269          s.Delete(i, num);
270          continue;
271        }
272      }
273      else
274      {
275        if (c1 == WCHAR_PATH_SEPARATOR || c1 == 0)
276        {
277          unsigned num = 2;
278          if (i != 0)
279            i--;
280          else if (c1 == 0)
281            num = 1;
282          s.Delete(i, num);
283          continue;
284        }
285      }
286    }
287    i++;
288  }
289}
290
291#endif // UNDER_CE
292
293#define LONG_PATH_DOTS_FOLDERS_PARSING
294
295
296/*
297Windows (at least 64-bit XP) can't resolve "." or ".." in paths that start with SuperPrefix \\?\
298To solve that problem we check such path:
299   - super path contains        "." or ".." - we use kSuperPathType_UseOnlySuper
300   - super path doesn't contain "." or ".." - we use kSuperPathType_UseOnlyMain
301*/
302#ifdef LONG_PATH_DOTS_FOLDERS_PARSING
303#ifndef UNDER_CE
304static bool AreThereDotsFolders(CFSTR s)
305{
306  for (unsigned i = 0;; i++)
307  {
308    FChar c = s[i];
309    if (c == 0)
310      return false;
311    if (c == '.' && (i == 0 || s[i - 1] == CHAR_PATH_SEPARATOR))
312    {
313      FChar c1 = s[i + 1];
314      if (c1 == 0 || c1 == CHAR_PATH_SEPARATOR ||
315          (c1 == '.' && (s[i + 2] == 0 || s[i + 2] == CHAR_PATH_SEPARATOR)))
316        return true;
317    }
318  }
319}
320#endif
321#endif // LONG_PATH_DOTS_FOLDERS_PARSING
322
323#ifdef WIN_LONG_PATH
324
325/*
326Most of Windows versions have problems, if some file or dir name
327contains '.' or ' ' at the end of name (Bad Path).
328To solve that problem, we always use Super Path ("\\?\" prefix and full path)
329in such cases. Note that "." and ".." are not bad names.
330
331There are 3 cases:
332  1) If the path is already Super Path, we use that path
333  2) If the path is not Super Path :
334     2.1) Bad Path;  we use only Super Path.
335     2.2) Good Path; we use Main Path. If it fails, we use Super Path.
336
337 NeedToUseOriginalPath returns:
338    kSuperPathType_UseOnlyMain    : Super already
339    kSuperPathType_UseOnlySuper    : not Super, Bad Path
340    kSuperPathType_UseMainAndSuper : not Super, Good Path
341*/
342
343int GetUseSuperPathType(CFSTR s) throw()
344{
345  if (IsSuperOrDevicePath(s))
346  {
347    #ifdef LONG_PATH_DOTS_FOLDERS_PARSING
348    if ((s)[2] != '.')
349      if (AreThereDotsFolders(s + kSuperPathPrefixSize))
350        return kSuperPathType_UseOnlySuper;
351    #endif
352    return kSuperPathType_UseOnlyMain;
353  }
354
355  for (unsigned i = 0;; i++)
356  {
357    FChar c = s[i];
358    if (c == 0)
359      return kSuperPathType_UseMainAndSuper;
360    if (c == '.' || c == ' ')
361    {
362      FChar c2 = s[i + 1];
363      if (c2 == 0 || c2 == CHAR_PATH_SEPARATOR)
364      {
365        // if it's "." or "..", it's not bad name.
366        if (c == '.')
367        {
368          if (i == 0 || s[i - 1] == CHAR_PATH_SEPARATOR)
369            continue;
370          if (s[i - 1] == '.')
371          {
372            if (i - 1 == 0 || s[i - 2] == CHAR_PATH_SEPARATOR)
373              continue;
374          }
375        }
376        return kSuperPathType_UseOnlySuper;
377      }
378    }
379  }
380}
381
382
383/*
384   returns false in two cases:
385     - if GetCurDir was used, and GetCurDir returned error.
386     - if we can't resolve ".." name.
387   if path is ".", "..", res is empty.
388   if it's Super Path already, res is empty.
389   for \**** , and if GetCurDir is not drive (c:\), res is empty
390   for absolute paths, returns true, res is Super path.
391*/
392
393
394static bool GetSuperPathBase(CFSTR s, UString &res)
395{
396  res.Empty();
397
398  FChar c = s[0];
399  if (c == 0)
400    return true;
401  if (c == '.' && (s[1] == 0 || (s[1] == '.' && s[2] == 0)))
402    return true;
403
404  if (IsSuperOrDevicePath(s))
405  {
406    #ifdef LONG_PATH_DOTS_FOLDERS_PARSING
407
408    if ((s)[2] == '.')
409      return true;
410
411    // we will return true here, so we will try to use these problem paths.
412
413    if (!AreThereDotsFolders(s + kSuperPathPrefixSize))
414      return true;
415
416    UString temp = fs2us(s);
417    unsigned fixedSize = GetRootPrefixSize_Of_SuperPath(temp);
418    if (fixedSize == 0)
419      return true;
420
421    UString rem = &temp[fixedSize];
422    if (!ResolveDotsFolders(rem))
423      return true;
424
425    temp.DeleteFrom(fixedSize);
426    res += temp;
427    res += rem;
428
429    #endif
430
431    return true;
432  }
433
434  if (c == CHAR_PATH_SEPARATOR)
435  {
436    if (s[1] == CHAR_PATH_SEPARATOR)
437    {
438      UString temp = fs2us(s + 2);
439      unsigned fixedSize = GetRootPrefixSize_Of_NetworkPath(temp);
440      if (fixedSize == 0) // maybe we must ignore that error to allow short network paths?
441        return false;
442      UString rem = &temp[fixedSize];
443      if (!ResolveDotsFolders(rem))
444        return false;
445      res += kSuperUncPrefix;
446      temp.DeleteFrom(fixedSize);
447      res += temp;
448      res += rem;
449      return true;
450    }
451  }
452  else
453  {
454    if (IsDrivePath(s))
455    {
456      UString temp = fs2us(s);
457      UString rem = &temp[kDrivePrefixSize];
458      if (!ResolveDotsFolders(rem))
459        return true;
460      res += kSuperPathPrefix;
461      temp.DeleteFrom(kDrivePrefixSize);
462      res += temp;
463      res += rem;
464      return true;
465    }
466  }
467
468  UString curDir;
469  if (!GetCurDir(curDir))
470    return false;
471  if (curDir.Back() != WCHAR_PATH_SEPARATOR)
472    curDir += WCHAR_PATH_SEPARATOR;
473
474  unsigned fixedSizeStart = 0;
475  unsigned fixedSize = 0;
476  const wchar_t *superMarker = NULL;
477  if (IsSuperPath(curDir))
478  {
479    fixedSize = GetRootPrefixSize_Of_SuperPath(curDir);
480    if (fixedSize == 0)
481      return false;
482  }
483  else
484  {
485    if (IsDrivePath(curDir))
486    {
487      superMarker = kSuperPathPrefix;
488      fixedSize = kDrivePrefixSize;
489    }
490    else
491    {
492      if (curDir[0] != CHAR_PATH_SEPARATOR || curDir[1] != CHAR_PATH_SEPARATOR)
493        return false;
494      fixedSizeStart = 2;
495      fixedSize = GetRootPrefixSize_Of_NetworkPath(&curDir[2]);
496      if (fixedSize == 0)
497        return false;
498      superMarker = kSuperUncPrefix;
499    }
500  }
501
502  UString temp;
503  if (c == CHAR_PATH_SEPARATOR)
504  {
505    temp = fs2us(s + 1);
506  }
507  else
508  {
509    temp += &curDir[fixedSizeStart + fixedSize];
510    temp += fs2us(s);
511  }
512  if (!ResolveDotsFolders(temp))
513    return false;
514  if (superMarker)
515    res += superMarker;
516  res += curDir.Mid(fixedSizeStart, fixedSize);
517  res += temp;
518  return true;
519}
520
521
522/*
523  In that case if GetSuperPathBase doesn't return new path, we don't need
524  to use same path that was used as main path
525
526  GetSuperPathBase  superPath.IsEmpty() onlyIfNew
527     false            *                *          GetCurDir Error
528     true            false             *          use Super path
529     true            true             true        don't use any path, we already used mainPath
530     true            true             false       use main path as Super Path, we don't try mainMath
531                                                  That case is possible now if GetCurDir returns unknow
532                                                  type of path (not drive and not network)
533
534  We can change that code if we want to try mainPath, if GetSuperPathBase returns error,
535  and we didn't try mainPath still.
536  If we want to work that way, we don't need to use GetSuperPathBase return code.
537*/
538
539bool GetSuperPath(CFSTR path, UString &superPath, bool onlyIfNew)
540{
541  if (GetSuperPathBase(path, superPath))
542  {
543    if (superPath.IsEmpty())
544    {
545      // actually the only possible when onlyIfNew == true and superPath is empty
546      // is case when
547
548      if (onlyIfNew)
549        return false;
550      superPath = fs2us(path);
551    }
552    return true;
553  }
554  return false;
555}
556
557bool GetSuperPaths(CFSTR s1, CFSTR s2, UString &d1, UString &d2, bool onlyIfNew)
558{
559  if (!GetSuperPathBase(s1, d1) ||
560      !GetSuperPathBase(s2, d2))
561    return false;
562  if (d1.IsEmpty() && d2.IsEmpty() && onlyIfNew)
563    return false;
564  if (d1.IsEmpty()) d1 = fs2us(s1);
565  if (d2.IsEmpty()) d2 = fs2us(s2);
566  return true;
567}
568
569
570/*
571// returns true, if we need additional use with New Super path.
572bool GetSuperPath(CFSTR path, UString &superPath)
573{
574  if (GetSuperPathBase(path, superPath))
575    return !superPath.IsEmpty();
576  return false;
577}
578*/
579#endif // WIN_LONG_PATH
580
581bool GetFullPath(CFSTR dirPrefix, CFSTR s, FString &res)
582{
583  res = s;
584
585  #ifdef UNDER_CE
586
587  if (s[0] != CHAR_PATH_SEPARATOR)
588  {
589    if (!dirPrefix)
590      return false;
591    res = dirPrefix;
592    res += s;
593  }
594
595  #else
596
597  unsigned prefixSize = GetRootPrefixSize(s);
598  if (prefixSize != 0)
599  {
600    if (!AreThereDotsFolders(s + prefixSize))
601      return true;
602
603    UString rem = fs2us(s + prefixSize);
604    if (!ResolveDotsFolders(rem))
605      return true; // maybe false;
606    res.DeleteFrom(prefixSize);
607    res += us2fs(rem);
608    return true;
609  }
610
611  /*
612  FChar c = s[0];
613  if (c == 0)
614    return true;
615  if (c == '.' && (s[1] == 0 || (s[1] == '.' && s[2] == 0)))
616    return true;
617  if (c == CHAR_PATH_SEPARATOR && s[1] == CHAR_PATH_SEPARATOR)
618    return true;
619  if (IsDrivePath(s))
620    return true;
621  */
622
623  UString curDir;
624  if (dirPrefix)
625    curDir = fs2us(dirPrefix);
626  else
627  {
628    if (!GetCurDir(curDir))
629      return false;
630  }
631  if (!curDir.IsEmpty() && curDir.Back() != WCHAR_PATH_SEPARATOR)
632    curDir += WCHAR_PATH_SEPARATOR;
633
634  unsigned fixedSize = 0;
635
636  #ifdef _WIN32
637
638  if (IsSuperPath(curDir))
639  {
640    fixedSize = GetRootPrefixSize_Of_SuperPath(curDir);
641    if (fixedSize == 0)
642      return false;
643  }
644  else
645  {
646    if (IsDrivePath(curDir))
647      fixedSize = kDrivePrefixSize;
648    else
649    {
650      if (curDir[0] != WCHAR_PATH_SEPARATOR || curDir[1] != WCHAR_PATH_SEPARATOR)
651        return false;
652      fixedSize = GetRootPrefixSize_Of_NetworkPath(&curDir[2]);
653      if (fixedSize == 0)
654        return false;
655      fixedSize += 2;
656    }
657  }
658
659  #endif // _WIN32
660
661  UString temp;
662  if (s[0] == CHAR_PATH_SEPARATOR)
663  {
664    temp = fs2us(s + 1);
665  }
666  else
667  {
668    temp += curDir.Ptr(fixedSize);
669    temp += fs2us(s);
670  }
671  if (!ResolveDotsFolders(temp))
672    return false;
673  curDir.DeleteFrom(fixedSize);
674  res = us2fs(curDir);
675  res += us2fs(temp);
676
677  #endif // UNDER_CE
678
679  return true;
680}
681
682bool GetFullPath(CFSTR path, FString &fullPath)
683{
684  return GetFullPath(NULL, path, fullPath);
685}
686
687}}}
688