// Windows/FileName.cpp | |
#include "StdAfx.h" | |
#include "FileName.h" | |
#ifndef _UNICODE | |
extern bool g_IsNT; | |
#endif | |
namespace NWindows { | |
namespace NFile { | |
namespace NName { | |
#ifndef USE_UNICODE_FSTRING | |
void NormalizeDirPathPrefix(FString &dirPath) | |
{ | |
if (dirPath.IsEmpty()) | |
return; | |
if (dirPath.Back() != FCHAR_PATH_SEPARATOR) | |
dirPath += FCHAR_PATH_SEPARATOR; | |
} | |
#endif | |
void NormalizeDirPathPrefix(UString &dirPath) | |
{ | |
if (dirPath.IsEmpty()) | |
return; | |
if (dirPath.Back() != WCHAR_PATH_SEPARATOR) | |
dirPath += WCHAR_PATH_SEPARATOR; | |
} | |
#ifdef _WIN32 | |
const wchar_t *kSuperPathPrefix = L"\\\\?\\"; | |
static const wchar_t *kSuperUncPrefix = L"\\\\?\\UNC\\"; | |
#define IS_DEVICE_PATH(s) ((s)[0] == '\\' && (s)[1] == '\\' && (s)[2] == '.' && (s)[3] == '\\') | |
#define IS_SUPER_PREFIX(s) ((s)[0] == '\\' && (s)[1] == '\\' && (s)[2] == '?' && (s)[3] == '\\') | |
#define IS_SUPER_OR_DEVICE_PATH(s) ((s)[0] == '\\' && (s)[1] == '\\' && ((s)[2] == '?' || (s)[2] == '.') && (s)[3] == '\\') | |
#define IS_LETTER_CHAR(c) ((c) >= 'a' && (c) <= 'z' || (c) >= 'A' && (c) <= 'Z') | |
#define IS_UNC_WITH_SLASH(s) ( \ | |
((s)[0] == 'U' || (s)[0] == 'u') && \ | |
((s)[1] == 'N' || (s)[1] == 'n') && \ | |
((s)[2] == 'C' || (s)[2] == 'c') && \ | |
(s)[3] == '\\') | |
bool IsDevicePath(CFSTR s) throw() | |
{ | |
#ifdef UNDER_CE | |
s = s; | |
return false; | |
/* | |
// actually we don't know the way to open device file in WinCE. | |
unsigned len = MyStringLen(s); | |
if (len < 5 || len > 5 || memcmp(s, FTEXT("DSK"), 3 * sizeof(FChar)) != 0) | |
return false; | |
if (s[4] != ':') | |
return false; | |
// for reading use SG_REQ sg; if (DeviceIoControl(dsk, IOCTL_DISK_READ)); | |
*/ | |
#else | |
if (!IS_DEVICE_PATH(s)) | |
return false; | |
unsigned len = MyStringLen(s); | |
if (len == 6 && s[5] == ':') | |
return true; | |
if (len < 18 || len > 22 || memcmp(s + kDevicePathPrefixSize, FTEXT("PhysicalDrive"), 13 * sizeof(FChar)) != 0) | |
return false; | |
for (unsigned i = 17; i < len; i++) | |
if (s[i] < '0' || s[i] > '9') | |
return false; | |
return true; | |
#endif | |
} | |
bool IsSuperUncPath(CFSTR s) throw() { return (IS_SUPER_PREFIX(s) && IS_UNC_WITH_SLASH(s + kSuperPathPrefixSize)); } | |
bool IsDrivePath(const wchar_t *s) throw() { return IS_LETTER_CHAR(s[0]) && s[1] == ':' && s[2] == '\\'; } | |
bool IsSuperPath(const wchar_t *s) throw() { return IS_SUPER_PREFIX(s); } | |
bool IsSuperOrDevicePath(const wchar_t *s) throw() { return IS_SUPER_OR_DEVICE_PATH(s); } | |
// bool IsSuperUncPath(const wchar_t *s) { return (IS_SUPER_PREFIX(s) && IS_UNC_WITH_SLASH(s + kSuperPathPrefixSize)); } | |
#ifndef USE_UNICODE_FSTRING | |
bool IsDrivePath(CFSTR s) throw() { return IS_LETTER_CHAR(s[0]) && s[1] == ':' && s[2] == '\\'; } | |
bool IsSuperPath(CFSTR s) throw() { return IS_SUPER_PREFIX(s); } | |
bool IsSuperOrDevicePath(CFSTR s) throw() { return IS_SUPER_OR_DEVICE_PATH(s); } | |
#endif // USE_UNICODE_FSTRING | |
bool IsAbsolutePath(const wchar_t *s) throw() | |
{ | |
return s[0] == WCHAR_PATH_SEPARATOR || IsDrivePath(s); | |
} | |
static const unsigned kDrivePrefixSize = 3; /* c:\ */ | |
#ifndef USE_UNICODE_FSTRING | |
static unsigned GetRootPrefixSize_Of_NetworkPath(CFSTR s) throw() | |
{ | |
// Network path: we look "server\path\" as root prefix | |
int pos = FindCharPosInString(s, '\\'); | |
if (pos < 0) | |
return 0; | |
int pos2 = FindCharPosInString(s + pos + 1, '\\'); | |
if (pos2 < 0) | |
return 0; | |
return pos + pos2 + 2; | |
} | |
static unsigned GetRootPrefixSize_Of_SimplePath(CFSTR s) throw() | |
{ | |
if (IsDrivePath(s)) | |
return kDrivePrefixSize; | |
if (s[0] != '\\' || s[1] != '\\') | |
return 0; | |
unsigned size = GetRootPrefixSize_Of_NetworkPath(s + 2); | |
return (size == 0) ? 0 : 2 + size; | |
} | |
static unsigned GetRootPrefixSize_Of_SuperPath(CFSTR s) throw() | |
{ | |
if (IS_UNC_WITH_SLASH(s + kSuperPathPrefixSize)) | |
{ | |
unsigned size = GetRootPrefixSize_Of_NetworkPath(s + kSuperUncPathPrefixSize); | |
return (size == 0) ? 0 : kSuperUncPathPrefixSize + size; | |
} | |
// we support \\?\c:\ paths and volume GUID paths \\?\Volume{GUID}\" | |
int pos = FindCharPosInString(s + kSuperPathPrefixSize, FCHAR_PATH_SEPARATOR); | |
if (pos < 0) | |
return 0; | |
return kSuperPathPrefixSize + pos + 1; | |
} | |
unsigned GetRootPrefixSize(CFSTR s) throw() | |
{ | |
if (IS_DEVICE_PATH(s)) | |
return kDevicePathPrefixSize; | |
if (IsSuperPath(s)) | |
return GetRootPrefixSize_Of_SuperPath(s); | |
return GetRootPrefixSize_Of_SimplePath(s); | |
} | |
#endif // USE_UNICODE_FSTRING | |
static unsigned GetRootPrefixSize_Of_NetworkPath(const wchar_t *s) throw() | |
{ | |
// Network path: we look "server\path\" as root prefix | |
int pos = FindCharPosInString(s, L'\\'); | |
if (pos < 0) | |
return 0; | |
int pos2 = FindCharPosInString(s + pos + 1, L'\\'); | |
if (pos2 < 0) | |
return 0; | |
return pos + pos2 + 2; | |
} | |
static unsigned GetRootPrefixSize_Of_SimplePath(const wchar_t *s) throw() | |
{ | |
if (IsDrivePath(s)) | |
return kDrivePrefixSize; | |
if (s[0] != '\\' || s[1] != '\\') | |
return 0; | |
unsigned size = GetRootPrefixSize_Of_NetworkPath(s + 2); | |
return (size == 0) ? 0 : 2 + size; | |
} | |
static unsigned GetRootPrefixSize_Of_SuperPath(const wchar_t *s) throw() | |
{ | |
if (IS_UNC_WITH_SLASH(s + kSuperPathPrefixSize)) | |
{ | |
unsigned size = GetRootPrefixSize_Of_NetworkPath(s + kSuperUncPathPrefixSize); | |
return (size == 0) ? 0 : kSuperUncPathPrefixSize + size; | |
} | |
// we support \\?\c:\ paths and volume GUID paths \\?\Volume{GUID}\" | |
int pos = FindCharPosInString(s + kSuperPathPrefixSize, L'\\'); | |
if (pos < 0) | |
return 0; | |
return kSuperPathPrefixSize + pos + 1; | |
} | |
unsigned GetRootPrefixSize(const wchar_t *s) throw() | |
{ | |
if (IS_DEVICE_PATH(s)) | |
return kDevicePathPrefixSize; | |
if (IsSuperPath(s)) | |
return GetRootPrefixSize_Of_SuperPath(s); | |
return GetRootPrefixSize_Of_SimplePath(s); | |
} | |
#else // _WIN32 | |
bool IsAbsolutePath(const wchar_t *s) throw() { return s[0] == WCHAR_PATH_SEPARATOR } | |
#ifndef USE_UNICODE_FSTRING | |
unsigned GetRootPrefixSize(CFSTR s) throw() { return s[0] == CHAR_PATH_SEPRATOR ? 1 : 0; } | |
#endif | |
unsigned GetRootPrefixSize(const wchar_t *s) throw() { return s[0] == CHAR_PATH_SEPRATOR ? 1 : 0; } | |
#endif // _WIN32 | |
#ifndef UNDER_CE | |
static bool GetCurDir(UString &path) | |
{ | |
path.Empty(); | |
DWORD needLength; | |
#ifndef _UNICODE | |
if (!g_IsNT) | |
{ | |
TCHAR s[MAX_PATH + 2]; | |
s[0] = 0; | |
needLength = ::GetCurrentDirectory(MAX_PATH + 1, s); | |
path = fs2us(fas2fs(s)); | |
} | |
else | |
#endif | |
{ | |
WCHAR s[MAX_PATH + 2]; | |
s[0] = 0; | |
needLength = ::GetCurrentDirectoryW(MAX_PATH + 1, s); | |
path = s; | |
} | |
return (needLength > 0 && needLength <= MAX_PATH); | |
} | |
static bool ResolveDotsFolders(UString &s) | |
{ | |
#ifdef _WIN32 | |
s.Replace(L'/', WCHAR_PATH_SEPARATOR); | |
#endif | |
for (int i = 0;;) | |
{ | |
wchar_t c = s[i]; | |
if (c == 0) | |
return true; | |
if (c == '.' && (i == 0 || s[i - 1] == WCHAR_PATH_SEPARATOR)) | |
{ | |
wchar_t c1 = s[i + 1]; | |
if (c1 == '.') | |
{ | |
wchar_t c2 = s[i + 2]; | |
if (c2 == WCHAR_PATH_SEPARATOR || c2 == 0) | |
{ | |
if (i == 0) | |
return false; | |
int k = i - 2; | |
for (; k >= 0; k--) | |
if (s[k] == WCHAR_PATH_SEPARATOR) | |
break; | |
unsigned num; | |
if (k >= 0) | |
{ | |
num = i + 2 - k; | |
i = k; | |
} | |
else | |
{ | |
num = (c2 == 0 ? (i + 2) : (i + 3)); | |
i = 0; | |
} | |
s.Delete(i, num); | |
continue; | |
} | |
} | |
else | |
{ | |
if (c1 == WCHAR_PATH_SEPARATOR || c1 == 0) | |
{ | |
unsigned num = 2; | |
if (i != 0) | |
i--; | |
else if (c1 == 0) | |
num = 1; | |
s.Delete(i, num); | |
continue; | |
} | |
} | |
} | |
i++; | |
} | |
} | |
#endif // UNDER_CE | |
#define LONG_PATH_DOTS_FOLDERS_PARSING | |
/* | |
Windows (at least 64-bit XP) can't resolve "." or ".." in paths that start with SuperPrefix \\?\ | |
To solve that problem we check such path: | |
- super path contains "." or ".." - we use kSuperPathType_UseOnlySuper | |
- super path doesn't contain "." or ".." - we use kSuperPathType_UseOnlyMain | |
*/ | |
#ifdef LONG_PATH_DOTS_FOLDERS_PARSING | |
#ifndef UNDER_CE | |
static bool AreThereDotsFolders(CFSTR s) | |
{ | |
for (unsigned i = 0;; i++) | |
{ | |
FChar c = s[i]; | |
if (c == 0) | |
return false; | |
if (c == '.' && (i == 0 || s[i - 1] == CHAR_PATH_SEPARATOR)) | |
{ | |
FChar c1 = s[i + 1]; | |
if (c1 == 0 || c1 == CHAR_PATH_SEPARATOR || | |
(c1 == '.' && (s[i + 2] == 0 || s[i + 2] == CHAR_PATH_SEPARATOR))) | |
return true; | |
} | |
} | |
} | |
#endif | |
#endif // LONG_PATH_DOTS_FOLDERS_PARSING | |
#ifdef WIN_LONG_PATH | |
/* | |
Most of Windows versions have problems, if some file or dir name | |
contains '.' or ' ' at the end of name (Bad Path). | |
To solve that problem, we always use Super Path ("\\?\" prefix and full path) | |
in such cases. Note that "." and ".." are not bad names. | |
There are 3 cases: | |
1) If the path is already Super Path, we use that path | |
2) If the path is not Super Path : | |
2.1) Bad Path; we use only Super Path. | |
2.2) Good Path; we use Main Path. If it fails, we use Super Path. | |
NeedToUseOriginalPath returns: | |
kSuperPathType_UseOnlyMain : Super already | |
kSuperPathType_UseOnlySuper : not Super, Bad Path | |
kSuperPathType_UseMainAndSuper : not Super, Good Path | |
*/ | |
int GetUseSuperPathType(CFSTR s) throw() | |
{ | |
if (IsSuperOrDevicePath(s)) | |
{ | |
#ifdef LONG_PATH_DOTS_FOLDERS_PARSING | |
if ((s)[2] != '.') | |
if (AreThereDotsFolders(s + kSuperPathPrefixSize)) | |
return kSuperPathType_UseOnlySuper; | |
#endif | |
return kSuperPathType_UseOnlyMain; | |
} | |
for (unsigned i = 0;; i++) | |
{ | |
FChar c = s[i]; | |
if (c == 0) | |
return kSuperPathType_UseMainAndSuper; | |
if (c == '.' || c == ' ') | |
{ | |
FChar c2 = s[i + 1]; | |
if (c2 == 0 || c2 == CHAR_PATH_SEPARATOR) | |
{ | |
// if it's "." or "..", it's not bad name. | |
if (c == '.') | |
{ | |
if (i == 0 || s[i - 1] == CHAR_PATH_SEPARATOR) | |
continue; | |
if (s[i - 1] == '.') | |
{ | |
if (i - 1 == 0 || s[i - 2] == CHAR_PATH_SEPARATOR) | |
continue; | |
} | |
} | |
return kSuperPathType_UseOnlySuper; | |
} | |
} | |
} | |
} | |
/* | |
returns false in two cases: | |
- if GetCurDir was used, and GetCurDir returned error. | |
- if we can't resolve ".." name. | |
if path is ".", "..", res is empty. | |
if it's Super Path already, res is empty. | |
for \**** , and if GetCurDir is not drive (c:\), res is empty | |
for absolute paths, returns true, res is Super path. | |
*/ | |
static bool GetSuperPathBase(CFSTR s, UString &res) | |
{ | |
res.Empty(); | |
FChar c = s[0]; | |
if (c == 0) | |
return true; | |
if (c == '.' && (s[1] == 0 || (s[1] == '.' && s[2] == 0))) | |
return true; | |
if (IsSuperOrDevicePath(s)) | |
{ | |
#ifdef LONG_PATH_DOTS_FOLDERS_PARSING | |
if ((s)[2] == '.') | |
return true; | |
// we will return true here, so we will try to use these problem paths. | |
if (!AreThereDotsFolders(s + kSuperPathPrefixSize)) | |
return true; | |
UString temp = fs2us(s); | |
unsigned fixedSize = GetRootPrefixSize_Of_SuperPath(temp); | |
if (fixedSize == 0) | |
return true; | |
UString rem = &temp[fixedSize]; | |
if (!ResolveDotsFolders(rem)) | |
return true; | |
temp.DeleteFrom(fixedSize); | |
res += temp; | |
res += rem; | |
#endif | |
return true; | |
} | |
if (c == CHAR_PATH_SEPARATOR) | |
{ | |
if (s[1] == CHAR_PATH_SEPARATOR) | |
{ | |
UString temp = fs2us(s + 2); | |
unsigned fixedSize = GetRootPrefixSize_Of_NetworkPath(temp); | |
if (fixedSize == 0) // maybe we must ignore that error to allow short network paths? | |
return false; | |
UString rem = &temp[fixedSize]; | |
if (!ResolveDotsFolders(rem)) | |
return false; | |
res += kSuperUncPrefix; | |
temp.DeleteFrom(fixedSize); | |
res += temp; | |
res += rem; | |
return true; | |
} | |
} | |
else | |
{ | |
if (IsDrivePath(s)) | |
{ | |
UString temp = fs2us(s); | |
UString rem = &temp[kDrivePrefixSize]; | |
if (!ResolveDotsFolders(rem)) | |
return true; | |
res += kSuperPathPrefix; | |
temp.DeleteFrom(kDrivePrefixSize); | |
res += temp; | |
res += rem; | |
return true; | |
} | |
} | |
UString curDir; | |
if (!GetCurDir(curDir)) | |
return false; | |
if (curDir.Back() != WCHAR_PATH_SEPARATOR) | |
curDir += WCHAR_PATH_SEPARATOR; | |
unsigned fixedSizeStart = 0; | |
unsigned fixedSize = 0; | |
const wchar_t *superMarker = NULL; | |
if (IsSuperPath(curDir)) | |
{ | |
fixedSize = GetRootPrefixSize_Of_SuperPath(curDir); | |
if (fixedSize == 0) | |
return false; | |
} | |
else | |
{ | |
if (IsDrivePath(curDir)) | |
{ | |
superMarker = kSuperPathPrefix; | |
fixedSize = kDrivePrefixSize; | |
} | |
else | |
{ | |
if (curDir[0] != CHAR_PATH_SEPARATOR || curDir[1] != CHAR_PATH_SEPARATOR) | |
return false; | |
fixedSizeStart = 2; | |
fixedSize = GetRootPrefixSize_Of_NetworkPath(&curDir[2]); | |
if (fixedSize == 0) | |
return false; | |
superMarker = kSuperUncPrefix; | |
} | |
} | |
UString temp; | |
if (c == CHAR_PATH_SEPARATOR) | |
{ | |
temp = fs2us(s + 1); | |
} | |
else | |
{ | |
temp += &curDir[fixedSizeStart + fixedSize]; | |
temp += fs2us(s); | |
} | |
if (!ResolveDotsFolders(temp)) | |
return false; | |
if (superMarker) | |
res += superMarker; | |
res += curDir.Mid(fixedSizeStart, fixedSize); | |
res += temp; | |
return true; | |
} | |
/* | |
In that case if GetSuperPathBase doesn't return new path, we don't need | |
to use same path that was used as main path | |
GetSuperPathBase superPath.IsEmpty() onlyIfNew | |
false * * GetCurDir Error | |
true false * use Super path | |
true true true don't use any path, we already used mainPath | |
true true false use main path as Super Path, we don't try mainMath | |
That case is possible now if GetCurDir returns unknow | |
type of path (not drive and not network) | |
We can change that code if we want to try mainPath, if GetSuperPathBase returns error, | |
and we didn't try mainPath still. | |
If we want to work that way, we don't need to use GetSuperPathBase return code. | |
*/ | |
bool GetSuperPath(CFSTR path, UString &superPath, bool onlyIfNew) | |
{ | |
if (GetSuperPathBase(path, superPath)) | |
{ | |
if (superPath.IsEmpty()) | |
{ | |
// actually the only possible when onlyIfNew == true and superPath is empty | |
// is case when | |
if (onlyIfNew) | |
return false; | |
superPath = fs2us(path); | |
} | |
return true; | |
} | |
return false; | |
} | |
bool GetSuperPaths(CFSTR s1, CFSTR s2, UString &d1, UString &d2, bool onlyIfNew) | |
{ | |
if (!GetSuperPathBase(s1, d1) || | |
!GetSuperPathBase(s2, d2)) | |
return false; | |
if (d1.IsEmpty() && d2.IsEmpty() && onlyIfNew) | |
return false; | |
if (d1.IsEmpty()) d1 = fs2us(s1); | |
if (d2.IsEmpty()) d2 = fs2us(s2); | |
return true; | |
} | |
/* | |
// returns true, if we need additional use with New Super path. | |
bool GetSuperPath(CFSTR path, UString &superPath) | |
{ | |
if (GetSuperPathBase(path, superPath)) | |
return !superPath.IsEmpty(); | |
return false; | |
} | |
*/ | |
#endif // WIN_LONG_PATH | |
bool GetFullPath(CFSTR dirPrefix, CFSTR s, FString &res) | |
{ | |
res = s; | |
#ifdef UNDER_CE | |
if (s[0] != CHAR_PATH_SEPARATOR) | |
{ | |
if (!dirPrefix) | |
return false; | |
res = dirPrefix; | |
res += s; | |
} | |
#else | |
unsigned prefixSize = GetRootPrefixSize(s); | |
if (prefixSize != 0) | |
{ | |
if (!AreThereDotsFolders(s + prefixSize)) | |
return true; | |
UString rem = fs2us(s + prefixSize); | |
if (!ResolveDotsFolders(rem)) | |
return true; // maybe false; | |
res.DeleteFrom(prefixSize); | |
res += us2fs(rem); | |
return true; | |
} | |
/* | |
FChar c = s[0]; | |
if (c == 0) | |
return true; | |
if (c == '.' && (s[1] == 0 || (s[1] == '.' && s[2] == 0))) | |
return true; | |
if (c == CHAR_PATH_SEPARATOR && s[1] == CHAR_PATH_SEPARATOR) | |
return true; | |
if (IsDrivePath(s)) | |
return true; | |
*/ | |
UString curDir; | |
if (dirPrefix) | |
curDir = fs2us(dirPrefix); | |
else | |
{ | |
if (!GetCurDir(curDir)) | |
return false; | |
} | |
if (!curDir.IsEmpty() && curDir.Back() != WCHAR_PATH_SEPARATOR) | |
curDir += WCHAR_PATH_SEPARATOR; | |
unsigned fixedSize = 0; | |
#ifdef _WIN32 | |
if (IsSuperPath(curDir)) | |
{ | |
fixedSize = GetRootPrefixSize_Of_SuperPath(curDir); | |
if (fixedSize == 0) | |
return false; | |
} | |
else | |
{ | |
if (IsDrivePath(curDir)) | |
fixedSize = kDrivePrefixSize; | |
else | |
{ | |
if (curDir[0] != WCHAR_PATH_SEPARATOR || curDir[1] != WCHAR_PATH_SEPARATOR) | |
return false; | |
fixedSize = GetRootPrefixSize_Of_NetworkPath(&curDir[2]); | |
if (fixedSize == 0) | |
return false; | |
fixedSize += 2; | |
} | |
} | |
#endif // _WIN32 | |
UString temp; | |
if (s[0] == CHAR_PATH_SEPARATOR) | |
{ | |
temp = fs2us(s + 1); | |
} | |
else | |
{ | |
temp += curDir.Ptr(fixedSize); | |
temp += fs2us(s); | |
} | |
if (!ResolveDotsFolders(temp)) | |
return false; | |
curDir.DeleteFrom(fixedSize); | |
res = us2fs(curDir); | |
res += us2fs(temp); | |
#endif // UNDER_CE | |
return true; | |
} | |
bool GetFullPath(CFSTR path, FString &fullPath) | |
{ | |
return GetFullPath(NULL, path, fullPath); | |
} | |
}}} |