diff --git a/src/base/path.cpp b/src/base/path.cpp index 14950d4739cd..f4162e040c37 100644 --- a/src/base/path.cpp +++ b/src/base/path.cpp @@ -54,6 +54,19 @@ namespace { 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'\\'); @@ -85,8 +98,6 @@ Path::Path(const std::string &pathStr) bool Path::isValid() const { - // does not support UNC path - if (isEmpty()) return false; @@ -96,6 +107,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(); @@ -128,6 +142,29 @@ 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); @@ -135,8 +172,10 @@ bool Path::exists() const 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; @@ -154,8 +193,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 {}; @@ -380,3 +428,17 @@ std::size_t qHash(const Path &key, const std::size_t seed) { return ::qHash(key.data(), seed); } + +#if defined(Q_OS_WIN) +bool isUNCPath(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 diff --git a/src/base/path.h b/src/base/path.h index 8e222bc64ca6..4fd127a38337 100644 --- a/src/base/path.h +++ b/src/base/path.h @@ -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; @@ -100,3 +105,7 @@ QDataStream &operator<<(QDataStream &out, const Path &path); QDataStream &operator>>(QDataStream &in, Path &path); std::size_t qHash(const Path &key, std::size_t seed = 0); + +#if defined(Q_OS_WIN) +bool isQStrUNCPath(const QString &path); +#endif diff --git a/src/base/utils/fs.cpp b/src/base/utils/fs.cpp index 4dd02e3ded9a..8094faedae2f 100644 --- a/src/base/utils/fs.cpp +++ b/src/base/utils/fs.cpp @@ -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) + std::wstring 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(); } diff --git a/test/testpath.cpp b/test/testpath.cpp index 6449a8071437..a96ad0e16c50 100644 --- a/test/testpath.cpp +++ b/test/testpath.cpp @@ -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 } @@ -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); @@ -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)); @@ -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());