Add support for "extended length" Windows paths to wxFileName

Recognize any paths starting with "\\?\" as this kind of paths instead
of only recognizing those in the form of "\\?\Volume{GUID}" as before
(see e01a788ee0 (Add support for MSW unique volume names to wxFileName.,
2009-12-05).
This commit is contained in:
Vadim Zeitlin
2025-01-16 00:55:42 +01:00
parent e80bcffe69
commit 95d2857308
4 changed files with 101 additions and 33 deletions
+4
View File
@@ -492,6 +492,10 @@ public:
// is the char a path separator for this format?
static bool IsPathSeparator(wxChar ch, wxPathFormat format = wxPATH_NATIVE);
// is this is a DOS path which begins with "\\?\"?
static bool IsMSWExtendedLengthPath(const wxString& path,
wxPathFormat format = wxPATH_NATIVE);
// is this is a DOS path which begins with a windows unique volume name
// ('\\?\Volume{guid}\')?
static bool IsMSWUniqueVolumeNamePath(const wxString& path,
+19 -2
View File
@@ -870,8 +870,11 @@ public:
- Just a single letter, for the usual drive letter volumes, e.g. @c C.
- A share name preceded by a double backslash, e.g. `\\share`.
- A GUID volume preceded by a double backslash and a question mark,
e.g. `\\?\Volume{12345678-9abc-def0-1234-56789abcdef0}`.
- The first part of a so-called "Windows NT device" path, also called
"extended length" path, in the form of `\\.\X:` or a raw volume path,
e.g. `\\?\Volume{12345678-9abc-def0-1234-56789abcdef0}`. Such volumes
always start with a double backslash and a question mark. See also
IsMSWExtendedLengthPath().
*/
wxString GetVolume() const;
@@ -1033,6 +1036,20 @@ public:
static bool IsPathSeparator(wxChar ch,
wxPathFormat format = wxPATH_NATIVE);
/**
Returns @true if the path starts with a double backslash and a question
mark.
Such paths are known as "Windows NT device" paths or "extended length"
paths and are passed directly to the file system, allowing to access
objects not accessible using the normal paths and avoiding the 260
character path length restriction.
@since 3.3.0
*/
static bool IsMSWExtendedLengthPath(const wxString& path,
wxPathFormat format = wxPATH_NATIVE);
/**
Returns @true if the volume part of the path is a unique volume name.
+77 -31
View File
@@ -134,8 +134,10 @@ namespace
// private constants
// ----------------------------------------------------------------------------
// length of \\?\Volume{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}\ string
static const size_t wxMSWUniqueVolumePrefixLength = 49;
// Prefix of MSW extended-length paths.
static constexpr char wxMSW_EXTENDED_PATH_PREFIX[] = R"(\\?\)";
static constexpr size_t
wxMSW_EXTENDED_PATH_PREFIX_LEN = WXSIZEOF(wxMSW_EXTENDED_PATH_PREFIX) - 1;
// ----------------------------------------------------------------------------
// private classes
@@ -300,14 +302,6 @@ static bool IsUNCPath(const wxString& path)
!IsDOSPathSep(path[2u]);
}
// return true if the string looks like a GUID volume path ("\\?\Volume{guid}\")
static bool IsVolumeGUIDPath(const wxString& path)
{
return path.length() >= wxMSWUniqueVolumePrefixLength &&
path.StartsWith(wxS("\\\\?\\Volume{")) &&
path[wxMSWUniqueVolumePrefixLength - 1] == wxFILE_SEP_PATH_DOS;
}
// Under Unix-ish systems (basically everything except Windows but we can't
// just test for non-__WIN32__ because Cygwin defines it, yet we want to use
// lstat() under it, so test for all the rest explicitly) we may work either
@@ -619,22 +613,44 @@ namespace
void RemoveTrailingSeparatorsFromPath(wxString& strPath)
{
// Windows fails to find directory named "c:\dir\" even if "c:\dir" exists,
// so remove all trailing backslashes from the path - but don't do this for
// the paths "d:\" (which are different from "d:"), for just "\" or for
// windows unique volume names ("\\?\Volume{GUID}\")
while ( wxEndsWithPathSeparator( strPath ) )
{
size_t len = strPath.length();
if ( len == 1 || (len == 3 && strPath[len - 2] == wxT(':')) ||
(len == wxMSWUniqueVolumePrefixLength &&
wxFileName::IsMSWUniqueVolumeNamePath(strPath)))
{
break;
}
// We should never have empty paths here, but skip them if we ever do.
if ( strPath.empty() )
return;
strPath.Truncate(len - 1);
// Windows fails to find directory named "c:\dir\" even if "c:\dir" exists,
// so remove all trailing backslashes from the path - but don't do this if
// it is the last slash in the path to avoid turning "d:\" into "d:" (which
// is a different path), turning "\" into nothing or making extended length
// paths invalid.
const auto lastNonSeparator = strPath.find_last_not_of(R"(\/)");
const auto firstTrailingSeparator =
lastNonSeparator == wxString::npos ? 0 : lastNonSeparator + 1;
if ( firstTrailingSeparator == strPath.length() )
{
// The path doesn't end with a separator, nothing to do.
return;
}
// Check if there any separators would remain if we removed all trailing
// ones, ignoring those that are part of fixed wxMSW_EXTENDED_PATH_PREFIX
// or UNC path prefix.
const auto lastButOneSeparator =
strPath.find_last_of(R"(\/)", lastNonSeparator);
if ( lastButOneSeparator == wxString::npos ||
(lastButOneSeparator == wxMSW_EXTENDED_PATH_PREFIX_LEN - 1 &&
strPath.StartsWith(wxMSW_EXTENDED_PATH_PREFIX)) ||
(lastButOneSeparator == 1 && IsUNCPath(strPath)) )
{
// The path doesn't contain any other separators, so don't remove all
// of them.
strPath.erase(firstTrailingSeparator + 1);
return;
}
// Remove all trailing separators.
strPath.erase(firstTrailingSeparator);
}
#endif // __WINDOWS_
@@ -1995,13 +2011,28 @@ bool wxFileName::IsPathSeparator(wxChar ch, wxPathFormat format)
return ch != wxT('\0') && GetPathSeparators(format).Find(ch) != wxNOT_FOUND;
}
/* static */
bool
wxFileName::IsMSWExtendedLengthPath(const wxString& path, wxPathFormat format)
{
return GetFormat(format) == wxPATH_DOS &&
path.StartsWith(wxMSW_EXTENDED_PATH_PREFIX);
}
/* static */
bool
wxFileName::IsMSWUniqueVolumeNamePath(const wxString& path, wxPathFormat format)
{
// length of \\?\Volume{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}\ string
constexpr size_t wxMSWUniqueVolumePrefixLength = 49;
// return true if the format used is the DOS/Windows one and the string begins
// with a Windows unique volume name ("\\?\Volume{guid}\")
return GetFormat(format) == wxPATH_DOS && IsVolumeGUIDPath(path);
return GetFormat(format) == wxPATH_DOS &&
path.length() >= wxMSWUniqueVolumePrefixLength &&
path.StartsWith(wxS("\\\\?\\Volume{")) &&
path[wxMSWUniqueVolumePrefixLength - 1] == wxFILE_SEP_PATH_DOS;
}
// ----------------------------------------------------------------------------
@@ -2367,18 +2398,33 @@ wxFileName::SplitVolume(const wxString& fullpath,
switch ( format )
{
case wxPATH_DOS:
// Deal with MSW UNC and volume GUID paths complications first.
if ( IsVolumeGUIDPath(fullpath) )
// Deal with MSW complications first: first, the special case of
// extended-length paths.
if ( fullpath.StartsWith(wxMSW_EXTENDED_PATH_PREFIX) )
{
// Find the next path separator after this prefix.
//
// Note that such paths contain only backslashes, never slashes.
const auto posNextSep =
fullpath.find(wxFILE_SEP_PATH_DOS,
wxMSW_EXTENDED_PATH_PREFIX_LEN);
// Note that this works even if posNextSep is npos.
if ( pstrVolume )
*pstrVolume = fullpath.Left(wxMSWUniqueVolumePrefixLength - 1);
// Note: take the first slash here.
pathOnly = fullpath.Mid(wxMSWUniqueVolumePrefixLength - 1);
*pstrVolume = fullpath(0, posNextSep);
// Extended-length paths must have a backslash after the volume
// but if they ever don't, still pretend that there is one at
// the end because this is not going to be a normal path
// anyhow, so this seems like the least useless thing we can do.
if ( posNextSep != wxString::npos )
pathOnly = fullpath.substr(posNextSep);
else
pathOnly = wxFILE_SEP_PATH_DOS;
break;
}
// Next check for UNC \\share\path syntax.
if ( IsUNCPath(fullpath) )
{
// Note that IsUNCPath() checks that 3rd character is not a
+1
View File
@@ -82,6 +82,7 @@ static struct TestFileNameInfo
{ "c:foo.bar", "c", "", "foo", "bar", false, wxPATH_DOS },
{ "c:\\foo.bar", "c", "\\", "foo", "bar", true, wxPATH_DOS },
{ "c:\\Windows\\command.com", "c", "\\Windows", "command", "com", true, wxPATH_DOS },
{ R"(\\?\c:\Windows\System32\cmd.exe)", R"(\\?\c:)", R"(\Windows\System32)", R"(cmd)", R"(exe)", true, wxPATH_DOS },
{ "\\\\?\\Volume{8089d7d7-d0ac-11db-9dd0-806d6172696f}\\",
"\\\\?\\Volume{8089d7d7-d0ac-11db-9dd0-806d6172696f}", "\\", "", "", true, wxPATH_DOS },
{ "\\\\?\\Volume{8089d7d7-d0ac-11db-9dd0-806d6172696f}\\Program Files\\setup.exe",