Skip to content

Commit

Permalink
Support UNC path capacity on windows
Browse files Browse the repository at this point in the history
  • Loading branch information
DoubleSpicy committed Jan 30, 2025
1 parent 82c36ae commit ccc2bad
Show file tree
Hide file tree
Showing 4 changed files with 112 additions and 6 deletions.
74 changes: 68 additions & 6 deletions src/base/path.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,35 @@ const int PATHLIST_TYPEID = qRegisterMetaType<PathList>("PathList");

namespace
{
#if defined(Q_OS_WIN)
bool isQStrUNCPath(const QString &path)
{
// strict format of \\host\drive\folder with no forbidden char to avoid conflict with normal path
if (path.left(2) != uR"(\\)"_s)
return false;
const auto lastSlash = path.lastIndexOf(u'\\');
if ((lastSlash - 1) <= 2)
return false;
const QRegularExpression regex {u"[\\0-\\37:?\"*<>|:/]"_s}; // no drive letter allowed C:/
return !regex.match(path.data()).hasMatch();
}
#endif

QString cleanPath(const QString &path)
{
#if defined(Q_OS_WIN)
// QT's cleanPath does not support UNC path
if (isQStrUNCPath(path))
{
// only trim off trailing \ which is meaningless
int index = path.size();
while (path[index - 1] == u'\\')
--index;
if (index <= (path.size() - 1))
return path.left(index);
return path;
}
#endif
const bool hasSeparator = std::any_of(path.cbegin(), path.cend(), [](const QChar c)
{
return (c == u'/') || (c == u'\\');
Expand Down Expand Up @@ -85,8 +112,6 @@ Path::Path(const std::string &pathStr)

bool Path::isValid() const
{
// does not support UNC path

if (isEmpty())
return false;

Expand All @@ -96,6 +121,9 @@ bool Path::isValid() const
if (hasDriveLetter(view))
view = view.mid(3);

if (isUNCPath())
return true;

// \\37 is using base-8 number system
const QRegularExpression regex {u"[\\0-\\37:?\"*<>|]"_s};
return !regex.match(view).hasMatch();
Expand Down Expand Up @@ -128,15 +156,40 @@ bool Path::isRelative() const
return QDir::isRelativePath(m_pathStr);
}

#if defined(Q_OS_WIN)
bool Path::isUNCPath() const
{
return isQStrUNCPath(data());
}

QString Path::getUNCRootPathStr() const
{
// only call this with isUNCPath() guard
int slashCount = 0;
int index = 0;
for (index = 0; index < m_pathStr.size(); ++index)
{
if (m_pathStr[index] == u'\\')
{
if (++slashCount == 4)
break;
}
}
return m_pathStr.left(index);
}
#endif

bool Path::exists() const
{
return !isEmpty() && QFileInfo::exists(m_pathStr);
}

Path Path::rootItem() const
{
// does not support UNC path

#ifdef Q_OS_WIN
if (isUNCPath())
return createUnchecked(getUNCRootPathStr());
#endif
const int slashIndex = m_pathStr.indexOf(u'/');
if (slashIndex < 0)
return *this;
Expand All @@ -154,8 +207,17 @@ Path Path::rootItem() const

Path Path::parentPath() const
{
// does not support UNC path

#ifdef Q_OS_WIN
if (isUNCPath())
{
// UNC path checks guarantee path in \\host\drive or \\host\drive\xxx format
const auto rootPath = getUNCRootPathStr();
if (rootPath == m_pathStr)
return createUnchecked(m_pathStr);
const int slashIndex = m_pathStr.lastIndexOf(u'\\');
return createUnchecked(m_pathStr.left(slashIndex));
}
#endif
const int slashIndex = m_pathStr.lastIndexOf(u'/');
if (slashIndex == -1)
return {};
Expand Down
5 changes: 5 additions & 0 deletions src/base/path.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@ class Path final
bool isAbsolute() const;
bool isRelative() const;

#if defined(Q_OS_WIN)
bool isUNCPath() const;
QString getUNCRootPathStr() const;
#endif

bool exists() const;

Path rootItem() const;
Expand Down
11 changes: 11 additions & 0 deletions src/base/utils/fs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,17 @@ Path Utils::Fs::toValidPath(const QString &name, const QString &pad)

qint64 Utils::Fs::freeDiskSpaceOnPath(const Path &path)
{
#if defined(Q_OS_WIN)
const auto wStrPath = path.data().toStdWString();
if (path.isUNCPath())
{
ULARGE_INTEGER FreeBytesAvailable = {0};
const BOOL ok = GetDiskFreeSpaceEx(wStrPath.c_str(), &FreeBytesAvailable, nullptr, nullptr);
if (ok)
return FreeBytesAvailable.QuadPart;
}
#endif

return QStorageInfo(path.data()).bytesAvailable();
}

Expand Down
28 changes: 28 additions & 0 deletions test/testpath.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@ private slots:
QVERIFY(Path(uR"(\\?\C:\)"_s) == Path(std::string(R"(\\?\C:\)")));

QVERIFY(Path(uR"(\\?\C:\abc)"_s) == Path(std::string(R"(\\?\C:\abc)")));

QVERIFY(Path(uR"(\\nas01\drive)"_s) == Path(std::string(R"(\\nas01\drive)")));
QVERIFY(Path(uR"(\\nas01\drive\xxx)"_s) == Path(std::string(R"(\\nas01\drive\xxx)")));
QVERIFY(Path(uR"(\\nas01\drive\xxx\\)"_s) == Path(std::string(R"(\\nas01\drive\xxx)")));
#endif
}

Expand Down Expand Up @@ -109,11 +113,27 @@ private slots:
QCOMPARE(Path(u"<"_s).isValid(), false);
QCOMPARE(Path(u">"_s).isValid(), false);
QCOMPARE(Path(u"|"_s).isValid(), false);

QCOMPARE(Path(uR"(\\nas01\drive)"_s).isValid(), true);
QCOMPARE(Path(uR"(\\nas01\drive\xxx)"_s).isValid(), true);
QCOMPARE(Path(uR"(\\nas01\drive\xxx\\)"_s).isValid(), true);
#else
QCOMPARE(Path(u"\0"_s).isValid(), false);
#endif
}

#ifdef Q_OS_WIN
void testIsUNCPath() const
{
QCOMPARE(Path(uR"(\\)"_s).isUNCPath(), false);
QCOMPARE(Path(uR"(\\\)"_s).isUNCPath(), false);
QCOMPARE(Path(uR"(\\nas01\drive\xxx)"_s).isUNCPath(), true);

QCOMPARE(Path(uR"(\\C:\\drive\xxx)"_s).isUNCPath(), false);
QCOMPARE(Path(uR"(\\nas01\drive\?)"_s).isUNCPath(), false);
QCOMPARE(Path(uR"(\\nas01\?\xxx)"_s).isUNCPath(), false);
}
#endif
void testIsEmpty() const
{
QCOMPARE(Path().isEmpty(), true);
Expand Down Expand Up @@ -247,6 +267,10 @@ private slots:
QCOMPARE(Path(uR"(c:\)"_s).rootItem(), Path(uR"(c:/)"_s));
QCOMPARE(Path(uR"(c:\a)"_s).rootItem(), Path(uR"(c:\)"_s));
QCOMPARE(Path(uR"(c:\a\b)"_s).rootItem(), Path(uR"(c:\)"_s));

QCOMPARE(Path(uR"(\\nas01\drive)"_s).rootItem(), Path(uR"(\\nas01\drive)"_s));
QCOMPARE(Path(uR"(\\nas01\drive\xxx)"_s).rootItem(), Path(uR"(\\nas01\drive)"_s));
QCOMPARE(Path(uR"(\\nas01\drive\xxx\yyy)"_s).rootItem(), Path(uR"(\\nas01\drive)"_s));
#else
QCOMPARE(Path(uR"(\a)"_s).rootItem(), Path(uR"(\a)"_s));
QCOMPARE(Path(uR"(\\a)"_s).rootItem(), Path(uR"(\\a)"_s));
Expand Down Expand Up @@ -280,6 +304,10 @@ private slots:
QCOMPARE(Path(uR"(c:\)"_s).parentPath(), Path());
QCOMPARE(Path(uR"(c:\a)"_s).parentPath(), Path(uR"(c:\)"_s));
QCOMPARE(Path(uR"(c:\a\b)"_s).parentPath(), Path(uR"(c:\a)"_s));

QCOMPARE(Path(uR"(\\nas01\drive)"_s).parentPath(), Path(uR"(\\nas01\drive)"_s));
QCOMPARE(Path(uR"(\\nas01\drive\xxx)"_s).parentPath(), Path(uR"(\\nas01\drive)"_s));
QCOMPARE(Path(uR"(\\nas01\drive\xxx\yyy)"_s).parentPath(), Path(uR"(\\nas01\drive\xxx)"_s));
#else
QCOMPARE(Path(uR"(\a)"_s).parentPath(), Path());
QCOMPARE(Path(uR"(\\a)"_s).parentPath(), Path());
Expand Down

0 comments on commit ccc2bad

Please sign in to comment.