mirror of
https://github.com/wxWidgets/wxWidgets.git
synced 2026-08-17 17:02:51 +08:00
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:
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user