From 43df7d0cd4bfb52c6581181b713ccf2eaef357ee Mon Sep 17 00:00:00 2001 From: Chocobo1 Date: Wed, 27 Jul 2022 01:14:03 +0800 Subject: [PATCH 1/6] Improve path validness test --- src/base/path.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/base/path.cpp b/src/base/path.cpp index 66270154c..bf8b8d481 100644 --- a/src/base/path.cpp +++ b/src/base/path.cpp @@ -76,12 +76,14 @@ bool Path::isValid() const if (isEmpty()) return false; + // https://stackoverflow.com/a/31976060 #if defined(Q_OS_WIN) - const QRegularExpression regex {u"[:?\"*<>|]"_qs}; + // \\37 is using base-8 number system + const QRegularExpression regex {u"[\\0-\\37:?\"*<>|]"_qs}; #elif defined(Q_OS_MACOS) const QRegularExpression regex {u"[\\0:]"_qs}; #else - const QRegularExpression regex {u"[\\0]"_qs}; + const QRegularExpression regex {u"\\0"_qs}; #endif return !m_pathStr.contains(regex); } From 2fa5ad982d9f50debcad0a5a0eb1fd7b38fe7ebf Mon Sep 17 00:00:00 2001 From: Chocobo1 Date: Wed, 27 Jul 2022 01:43:23 +0800 Subject: [PATCH 2/6] Improve absolute/relative path detection --- src/base/path.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/base/path.cpp b/src/base/path.cpp index bf8b8d481..21437b19d 100644 --- a/src/base/path.cpp +++ b/src/base/path.cpp @@ -95,11 +95,17 @@ bool Path::isEmpty() const bool Path::isAbsolute() const { + // `QDir::isAbsolutePath` treats `:` as a path to QResource, so handle it manually + if (m_pathStr.startsWith(u':')) + return false; return QDir::isAbsolutePath(m_pathStr); } bool Path::isRelative() const { + // `QDir::isRelativePath` treats `:` as a path to QResource, so handle it manually + if (m_pathStr.startsWith(u':')) + return true; return QDir::isRelativePath(m_pathStr); } From 5abd72d42ad1f2693b95ebf33c5a9423a8927d0b Mon Sep 17 00:00:00 2001 From: Chocobo1 Date: Thu, 28 Jul 2022 16:18:46 +0800 Subject: [PATCH 3/6] Add comments about "UNC path" support --- src/base/path.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/base/path.cpp b/src/base/path.cpp index 21437b19d..6053e8f53 100644 --- a/src/base/path.cpp +++ b/src/base/path.cpp @@ -73,6 +73,8 @@ Path::Path(const std::string &pathStr) bool Path::isValid() const { + // does not support UNC path + if (isEmpty()) return false; @@ -116,6 +118,8 @@ bool Path::exists() const Path Path::rootItem() const { + // does not support UNC path + const int slashIndex = m_pathStr.indexOf(u'/'); if (slashIndex < 0) return *this; @@ -133,6 +137,8 @@ Path Path::rootItem() const Path Path::parentPath() const { + // does not support UNC path + const int slashIndex = m_pathStr.lastIndexOf(u'/'); if (slashIndex == -1) return {}; From 16482c507b6296b526bfd06c5a564c4d9dd8d636 Mon Sep 17 00:00:00 2001 From: Chocobo1 Date: Fri, 12 Aug 2022 18:04:51 +0800 Subject: [PATCH 4/6] Fix drive letter validness check --- src/base/path.cpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/base/path.cpp b/src/base/path.cpp index 6053e8f53..867548beb 100644 --- a/src/base/path.cpp +++ b/src/base/path.cpp @@ -59,6 +59,14 @@ namespace }); return hasSeparator ? QDir::cleanPath(path) : path; } + +#ifdef Q_OS_WIN + bool hasDriveLetter(const QStringView path) + { + const QRegularExpression driveLetterRegex {u"^[A-Za-z]:/"_qs}; + return driveLetterRegex.match(path).hasMatch(); + } +#endif } Path::Path(const QString &pathStr) @@ -80,8 +88,13 @@ bool Path::isValid() const // https://stackoverflow.com/a/31976060 #if defined(Q_OS_WIN) + QStringView view = m_pathStr; + if (hasDriveLetter(view)) + view = view.mid(3); + // \\37 is using base-8 number system const QRegularExpression regex {u"[\\0-\\37:?\"*<>|]"_qs}; + return !regex.match(view).hasMatch(); #elif defined(Q_OS_MACOS) const QRegularExpression regex {u"[\\0:]"_qs}; #else From d7d1a90de612f55e9a519024296a9b5a8371bd7d Mon Sep 17 00:00:00 2001 From: Chocobo1 Date: Sat, 13 Aug 2022 12:32:07 +0800 Subject: [PATCH 5/6] Fix wrong parent path --- src/base/path.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/base/path.cpp b/src/base/path.cpp index 867548beb..51bea7694 100644 --- a/src/base/path.cpp +++ b/src/base/path.cpp @@ -162,8 +162,8 @@ Path Path::parentPath() const #ifdef Q_OS_WIN // should be `c:/` instead of `c:` // Windows "drive letter" is limited to one alphabet - if ((slashIndex == 2) && (m_pathStr.at(1) == u':')) - return createUnchecked(m_pathStr.left(slashIndex + 1)); + if ((slashIndex == 2) && hasDriveLetter(m_pathStr)) + return (m_pathStr.size() == 3) ? Path() : createUnchecked(m_pathStr.left(slashIndex + 1)); #endif return createUnchecked(m_pathStr.left(slashIndex)); } From f5836c9fc9ff8b87225e97aa138e20a860e634cc Mon Sep 17 00:00:00 2001 From: Chocobo1 Date: Sat, 13 Aug 2022 12:32:18 +0800 Subject: [PATCH 6/6] Add unit testing for Path class --- test/CMakeLists.txt | 1 + test/testpath.cpp | 293 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 294 insertions(+) create mode 100644 test/testpath.cpp diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 266e42040..ad00a4bdc 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -12,6 +12,7 @@ include_directories("../src") set(testFiles testalgorithm.cpp testorderedset.cpp + testpath.cpp testutilscompare.cpp testutilsgzip.cpp testutilsstring.cpp diff --git a/test/testpath.cpp b/test/testpath.cpp new file mode 100644 index 000000000..7dd7f3b6e --- /dev/null +++ b/test/testpath.cpp @@ -0,0 +1,293 @@ +/* + * Bittorrent Client using Qt and libtorrent. + * Copyright (C) 2022 Mike Tzou (Chocobo1) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * In addition, as a special exception, the copyright holders give permission to + * link this program with the OpenSSL project's "OpenSSL" library (or with + * modified versions of it that use the same license as the "OpenSSL" library), + * and distribute the linked executables. You must obey the GNU General Public + * License in all respects for all of the code used other than "OpenSSL". If you + * modify file(s), you may extend this exception to your version of the file(s), + * but you are not obligated to do so. If you do not wish to do so, delete this + * exception statement from your version. + */ + +#include +#include + +#include "base/global.h" +#include "base/path.h" + +class TestPath final : public QObject +{ + Q_OBJECT + Q_DISABLE_COPY_MOVE(TestPath) + +public: + TestPath() = default; + +private slots: + void testConstructors() const + { + QVERIFY(Path(u""_qs) == Path(std::string(""))); + QVERIFY(Path(u"abc"_qs) == Path(std::string("abc"))); + QVERIFY(Path(u"/abc"_qs) == Path(std::string("/abc"))); + QVERIFY(Path(uR"(\abc)"_qs) == Path(std::string(R"(\abc)"))); + +#ifdef Q_OS_WIN + QVERIFY(Path(uR"(c:)"_qs) == Path(std::string(R"(c:)"))); + QVERIFY(Path(uR"(c:/)"_qs) == Path(std::string(R"(c:/)"))); + QVERIFY(Path(uR"(c:/)"_qs) == Path(std::string(R"(c:\)"))); + QVERIFY(Path(uR"(c:\)"_qs) == Path(std::string(R"(c:/)"))); + QVERIFY(Path(uR"(c:\)"_qs) == Path(std::string(R"(c:\)"))); + + QVERIFY(Path(uR"(\\?\C:)"_qs) == Path(std::string(R"(\\?\C:)"))); + QVERIFY(Path(uR"(\\?\C:/)"_qs) == Path(std::string(R"(\\?\C:/)"))); + QVERIFY(Path(uR"(\\?\C:/)"_qs) == Path(std::string(R"(\\?\C:\)"))); + QVERIFY(Path(uR"(\\?\C:\)"_qs) == Path(std::string(R"(\\?\C:/)"))); + QVERIFY(Path(uR"(\\?\C:\)"_qs) == Path(std::string(R"(\\?\C:\)"))); + + QVERIFY(Path(uR"(\\?\C:\abc)"_qs) == Path(std::string(R"(\\?\C:\abc)"))); +#endif + } + + void testIsValid() const + { + QCOMPARE(Path().isValid(), false); + QCOMPARE(Path(u""_qs).isValid(), false); + + QCOMPARE(Path(u"/"_qs).isValid(), true); + QCOMPARE(Path(uR"(\)"_qs).isValid(), true); + + QCOMPARE(Path(u"a"_qs).isValid(), true); + QCOMPARE(Path(u"/a"_qs).isValid(), true); + QCOMPARE(Path(uR"(\a)"_qs).isValid(), true); + + QCOMPARE(Path(u"a/b"_qs).isValid(), true); + QCOMPARE(Path(uR"(a\b)"_qs).isValid(), true); + QCOMPARE(Path(u"/a/b"_qs).isValid(), true); + QCOMPARE(Path(uR"(/a\b)"_qs).isValid(), true); + QCOMPARE(Path(uR"(\a/b)"_qs).isValid(), true); + QCOMPARE(Path(uR"(\a\b)"_qs).isValid(), true); + + QCOMPARE(Path(u"//"_qs).isValid(), true); + QCOMPARE(Path(uR"(\\)"_qs).isValid(), true); + QCOMPARE(Path(u"//a"_qs).isValid(), true); + QCOMPARE(Path(uR"(\\a)"_qs).isValid(), true); + +#if defined Q_OS_MACOS + QCOMPARE(Path(u"\0"_qs).isValid(), false); + QCOMPARE(Path(u":"_qs).isValid(), false); +#elif defined Q_OS_WIN + QCOMPARE(Path(u"c:"_qs).isValid(), false); + QCOMPARE(Path(u"c:/"_qs).isValid(), true); + QCOMPARE(Path(uR"(c:\)"_qs).isValid(), true); + QCOMPARE(Path(uR"(c:\a)"_qs).isValid(), true); + QCOMPARE(Path(uR"(c:\a\b)"_qs).isValid(), true); + + for (int i = 0; i <= 31; ++i) + QCOMPARE(Path(QChar(i)).isValid(), false); + QCOMPARE(Path(u":"_qs).isValid(), false); + QCOMPARE(Path(u"?"_qs).isValid(), false); + QCOMPARE(Path(u"\""_qs).isValid(), false); + QCOMPARE(Path(u"*"_qs).isValid(), false); + QCOMPARE(Path(u"<"_qs).isValid(), false); + QCOMPARE(Path(u">"_qs).isValid(), false); + QCOMPARE(Path(u"|"_qs).isValid(), false); +#else + QCOMPARE(Path(u"\0"_qs).isValid(), false); +#endif + } + + void testIsEmpty() const + { + QCOMPARE(Path().isEmpty(), true); + QCOMPARE(Path(u""_qs).isEmpty(), true); + + QCOMPARE(Path(u"\0"_qs).isEmpty(), false); + + QCOMPARE(Path(u"/"_qs).isEmpty(), false); + QCOMPARE(Path(uR"(\)"_qs).isEmpty(), false); + + QCOMPARE(Path(u"a"_qs).isEmpty(), false); + QCOMPARE(Path(u"/a"_qs).isEmpty(), false); + QCOMPARE(Path(uR"(\a)"_qs).isEmpty(), false); + + QCOMPARE(Path(uR"(c:)"_qs).isEmpty(), false); + QCOMPARE(Path(uR"(c:/)"_qs).isEmpty(), false); + QCOMPARE(Path(uR"(c:\)"_qs).isEmpty(), false); + } + + void testIsAbsolute() const + { + QCOMPARE(Path().isAbsolute(), false); + QCOMPARE(Path(u""_qs).isAbsolute(), false); + +#ifdef Q_OS_WIN + QCOMPARE(Path(u"/"_qs).isAbsolute(), true); + QCOMPARE(Path(uR"(\)"_qs).isAbsolute(), true); + + QCOMPARE(Path(u"a"_qs).isAbsolute(), false); + QCOMPARE(Path(u"/a"_qs).isAbsolute(), true); + QCOMPARE(Path(uR"(\a)"_qs).isAbsolute(), true); + + QCOMPARE(Path(u"//"_qs).isAbsolute(), true); + QCOMPARE(Path(uR"(\\)"_qs).isAbsolute(), true); + QCOMPARE(Path(u"//a"_qs).isAbsolute(), true); + QCOMPARE(Path(uR"(\\a)"_qs).isAbsolute(), true); + + QCOMPARE(Path(uR"(c:)"_qs).isAbsolute(), true); + QCOMPARE(Path(uR"(c:/)"_qs).isAbsolute(), true); + QCOMPARE(Path(uR"(c:\)"_qs).isAbsolute(), true); + QCOMPARE(Path(uR"(c:\a)"_qs).isAbsolute(), true); + QCOMPARE(Path(uR"(c:\a\b)"_qs).isAbsolute(), true); + + QCOMPARE(Path(uR"(\\?\C:)"_qs).isAbsolute(), true); + QCOMPARE(Path(uR"(\\?\C:/)"_qs).isAbsolute(), true); + QCOMPARE(Path(uR"(\\?\C:\)"_qs).isAbsolute(), true); + QCOMPARE(Path(uR"(\\?\C:\a)"_qs).isAbsolute(), true); + QCOMPARE(Path(uR"(\\?\C:\a\b)"_qs).isAbsolute(), true); +#else + QCOMPARE(Path(u"/"_qs).isAbsolute(), true); + QCOMPARE(Path(uR"(\)"_qs).isAbsolute(), false); + + QCOMPARE(Path(u"a"_qs).isAbsolute(), false); + QCOMPARE(Path(u"/a"_qs).isAbsolute(), true); + QCOMPARE(Path(uR"(\a)"_qs).isAbsolute(), false); + + QCOMPARE(Path(u"//"_qs).isAbsolute(), true); + QCOMPARE(Path(uR"(\\)"_qs).isAbsolute(), false); + QCOMPARE(Path(u"//a"_qs).isAbsolute(), true); + QCOMPARE(Path(uR"(\\a)"_qs).isAbsolute(), false); +#endif + } + + void testIsRelative() const + { + QCOMPARE(Path().isRelative(), true); + QCOMPARE(Path(u""_qs).isRelative(), true); + +#if defined Q_OS_WIN + QCOMPARE(Path(u"/"_qs).isRelative(), false); + QCOMPARE(Path(uR"(\)"_qs).isRelative(), false); + + QCOMPARE(Path(u"a"_qs).isRelative(), true); + QCOMPARE(Path(u"/a"_qs).isRelative(), false); + QCOMPARE(Path(uR"(\a)"_qs).isRelative(), false); + + QCOMPARE(Path(u"//"_qs).isRelative(), false); + QCOMPARE(Path(uR"(\\)"_qs).isRelative(), false); + QCOMPARE(Path(u"//a"_qs).isRelative(), false); + QCOMPARE(Path(uR"(\\a)"_qs).isRelative(), false); + + QCOMPARE(Path(uR"(c:)"_qs).isRelative(), false); + QCOMPARE(Path(uR"(c:/)"_qs).isRelative(), false); + QCOMPARE(Path(uR"(c:\)"_qs).isRelative(), false); + QCOMPARE(Path(uR"(c:\a)"_qs).isRelative(), false); + QCOMPARE(Path(uR"(c:\a\b)"_qs).isRelative(), false); + + QCOMPARE(Path(uR"(\\?\C:)"_qs).isRelative(), false); + QCOMPARE(Path(uR"(\\?\C:/)"_qs).isRelative(), false); + QCOMPARE(Path(uR"(\\?\C:\)"_qs).isRelative(), false); + QCOMPARE(Path(uR"(\\?\C:\a)"_qs).isRelative(), false); + QCOMPARE(Path(uR"(\\?\C:\a\b)"_qs).isRelative(), false); +#else + QCOMPARE(Path(u"/"_qs).isRelative(), false); + QCOMPARE(Path(uR"(\)"_qs).isRelative(), true); + + QCOMPARE(Path(u"a"_qs).isRelative(), true); + QCOMPARE(Path(u"/a"_qs).isRelative(), false); + QCOMPARE(Path(uR"(\a)"_qs).isRelative(), true); + + QCOMPARE(Path(u"//"_qs).isRelative(), false); + QCOMPARE(Path(uR"(\\)"_qs).isRelative(), true); + QCOMPARE(Path(u"//a"_qs).isRelative(), false); + QCOMPARE(Path(uR"(\\a)"_qs).isRelative(), true); +#endif + } + + void testRootItem() const + { + QCOMPARE(Path().rootItem(), Path()); + QCOMPARE(Path(u""_qs).rootItem(), Path()); + + QCOMPARE(Path(u"/"_qs).rootItem(), Path(u"/"_qs)); + QCOMPARE(Path(uR"(\)"_qs).rootItem(), Path(uR"(\)"_qs)); + + QCOMPARE(Path(u"a"_qs).rootItem(), Path(u"a"_qs)); + QCOMPARE(Path(u"/a"_qs).rootItem(), Path(u"/"_qs)); + QCOMPARE(Path(u"/a/b"_qs).rootItem(), Path(u"/"_qs)); + + QCOMPARE(Path(u"//"_qs).rootItem(), Path(u"/"_qs)); + QCOMPARE(Path(uR"(\\)"_qs).rootItem(), Path(uR"(\\)"_qs)); + QCOMPARE(Path(u"//a"_qs).rootItem(), Path(u"/"_qs)); + +#ifdef Q_OS_WIN + QCOMPARE(Path(uR"(\a)"_qs).rootItem(), Path(uR"(\)"_qs)); + QCOMPARE(Path(uR"(\\a)"_qs).rootItem(), Path(uR"(\)"_qs)); + + QCOMPARE(Path(uR"(c:)"_qs).rootItem(), Path(uR"(c:)"_qs)); + QCOMPARE(Path(uR"(c:/)"_qs).rootItem(), Path(uR"(c:/)"_qs)); + QCOMPARE(Path(uR"(c:\)"_qs).rootItem(), Path(uR"(c:\)"_qs)); + QCOMPARE(Path(uR"(c:\)"_qs).rootItem(), Path(uR"(c:/)"_qs)); + QCOMPARE(Path(uR"(c:\a)"_qs).rootItem(), Path(uR"(c:\)"_qs)); + QCOMPARE(Path(uR"(c:\a\b)"_qs).rootItem(), Path(uR"(c:\)"_qs)); +#else + QCOMPARE(Path(uR"(\a)"_qs).rootItem(), Path(uR"(\a)"_qs)); + QCOMPARE(Path(uR"(\\a)"_qs).rootItem(), Path(uR"(\\a)"_qs)); +#endif + } + + void testParentPath() const + { + QCOMPARE(Path().parentPath(), Path()); + QCOMPARE(Path(u""_qs).parentPath(), Path()); + + QCOMPARE(Path(u"/"_qs).parentPath(), Path()); + QCOMPARE(Path(uR"(\)"_qs).parentPath(), Path()); + + QCOMPARE(Path(u"a"_qs).parentPath(), Path()); + QCOMPARE(Path(u"/a"_qs).parentPath(), Path(u"/"_qs)); + + QCOMPARE(Path(u"//"_qs).parentPath(), Path()); + QCOMPARE(Path(uR"(\\)"_qs).parentPath(), Path()); + QCOMPARE(Path(u"//a"_qs).parentPath(), Path(u"/"_qs)); + + QCOMPARE(Path(u"a/b"_qs).parentPath(), Path(u"a"_qs)); + +#ifdef Q_OS_WIN + QCOMPARE(Path(uR"(\a)"_qs).parentPath(), Path(uR"(\)"_qs)); + QCOMPARE(Path(uR"(\\a)"_qs).parentPath(), Path(uR"(\)"_qs)); + QCOMPARE(Path(uR"(a\b)"_qs).parentPath(), Path(u"a"_qs)); + + QCOMPARE(Path(uR"(c:)"_qs).parentPath(), Path()); + QCOMPARE(Path(uR"(c:/)"_qs).parentPath(), Path()); + QCOMPARE(Path(uR"(c:\)"_qs).parentPath(), Path()); + QCOMPARE(Path(uR"(c:\a)"_qs).parentPath(), Path(uR"(c:\)"_qs)); + QCOMPARE(Path(uR"(c:\a\b)"_qs).parentPath(), Path(uR"(c:\a)"_qs)); +#else + QCOMPARE(Path(uR"(\a)"_qs).parentPath(), Path()); + QCOMPARE(Path(uR"(\\a)"_qs).parentPath(), Path()); + QCOMPARE(Path(uR"(a\b)"_qs).parentPath(), Path()); +#endif + } + + // TODO: add tests for remaining methods +}; + +QTEST_APPLESS_MAIN(TestPath) +#include "testpath.moc"