From abd6d1f2f4a0dc732236a27890a8e6358637e488 Mon Sep 17 00:00:00 2001 From: cocopaw <223083270+cocopaw@users.noreply.github.com> Date: Mon, 4 Aug 2025 19:53:39 +1000 Subject: [PATCH 1/4] Add validation to block renaming to invalid names (e.g., '.', '..', control chars, length > 255) --- src/gui/torrentcontentmodel.cpp | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/src/gui/torrentcontentmodel.cpp b/src/gui/torrentcontentmodel.cpp index 2df7b8176..1794409a1 100644 --- a/src/gui/torrentcontentmodel.cpp +++ b/src/gui/torrentcontentmodel.cpp @@ -54,6 +54,8 @@ #include "base/global.h" #include "base/path.h" #include "base/utils/fs.h" +#include "base/utils/misc.h" +#include "base/utils/string.h" #include "torrentcontentmodelfile.h" #include "torrentcontentmodelfolder.h" #include "torrentcontentmodelitem.h" @@ -299,9 +301,27 @@ bool TorrentContentModel::setData(const QModelIndex &index, const QVariant &valu case TorrentContentModelItem::COL_NAME: { const QString currentName = item->name(); - const QString newName = value.toString(); + QString newName = value.toString().trimmed(); if (currentName != newName) { + bool invalid = newName.isEmpty() || (newName == u"."_s) || (newName == u".."_s) || (newName.length() > 255); + if (!invalid) + { + for (const QChar &c : newName) + { + const ushort unicode = c.unicode(); + if ((unicode < 32) || (unicode == 127) || (c == u'<') || (c == u'>') || (c == u':') || (c == u'"') || (c == u'/') || (c == u'\\') || (c == u'|') || (c == u'?') || (c == u'*')) + { + invalid = true; + break; + } + } + } + if (invalid) + { + emit renameFailed(tr("The name \"%1\" is invalid.").arg(newName)); + return false; + } try { const Path parentPath = getItemPath(index.parent()); @@ -710,4 +730,4 @@ void TorrentContentModel::notifySubtreeUpdated(const QModelIndex &index, const Q parentIndexes.push_back(sibling); } } -} +} \ No newline at end of file From 5b954a8542e3ace18f92a2293f7769a21a816cc8 Mon Sep 17 00:00:00 2001 From: cocopaw <223083270+cocopaw@users.noreply.github.com> Date: Tue, 5 Aug 2025 00:06:17 +1000 Subject: [PATCH 2/4] Add newline at end of torrentcontentmodel.cpp to comply with coding standards --- src/gui/torrentcontentmodel.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/torrentcontentmodel.cpp b/src/gui/torrentcontentmodel.cpp index 1794409a1..a3dbd561b 100644 --- a/src/gui/torrentcontentmodel.cpp +++ b/src/gui/torrentcontentmodel.cpp @@ -730,4 +730,4 @@ void TorrentContentModel::notifySubtreeUpdated(const QModelIndex &index, const Q parentIndexes.push_back(sibling); } } -} \ No newline at end of file +} From 365e1e6f56438614a0a7f8aedf1edbda36fb8884 Mon Sep 17 00:00:00 2001 From: cocopaw <223083270+cocopaw@users.noreply.github.com> Date: Thu, 7 Aug 2025 19:36:44 +1000 Subject: [PATCH 3/4] Added blocking of names ending in . --- src/gui/torrentcontentmodel.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/gui/torrentcontentmodel.cpp b/src/gui/torrentcontentmodel.cpp index a3dbd561b..3c91137b3 100644 --- a/src/gui/torrentcontentmodel.cpp +++ b/src/gui/torrentcontentmodel.cpp @@ -302,9 +302,10 @@ bool TorrentContentModel::setData(const QModelIndex &index, const QVariant &valu { const QString currentName = item->name(); QString newName = value.toString().trimmed(); + if (currentName != newName) { - bool invalid = newName.isEmpty() || (newName == u"."_s) || (newName == u".."_s) || (newName.length() > 255); + bool invalid = newName.isEmpty() || (newName == u"."_s) || (newName == u".."_s) || (newName.length() > 255) || newName.endsWith(u'.'); if (!invalid) { for (const QChar &c : newName) From 149bdad2854970c7ab0b185dddf4555180f306a6 Mon Sep 17 00:00:00 2001 From: cocopaw <223083270+cocopaw@users.noreply.github.com> Date: Sat, 16 Aug 2025 16:44:09 +1000 Subject: [PATCH 4/4] Add platform-specific handling. Add Win reserved name chacking for CON,PRN etc --- src/gui/torrentcontentmodel.cpp | 72 ++++++++++++++++++++++++--------- src/gui/torrentcontentmodel.h | 1 + 2 files changed, 55 insertions(+), 18 deletions(-) diff --git a/src/gui/torrentcontentmodel.cpp b/src/gui/torrentcontentmodel.cpp index 3c91137b3..734a4c0b5 100644 --- a/src/gui/torrentcontentmodel.cpp +++ b/src/gui/torrentcontentmodel.cpp @@ -54,8 +54,6 @@ #include "base/global.h" #include "base/path.h" #include "base/utils/fs.h" -#include "base/utils/misc.h" -#include "base/utils/string.h" #include "torrentcontentmodelfile.h" #include "torrentcontentmodelfolder.h" #include "torrentcontentmodelitem.h" @@ -275,6 +273,56 @@ int TorrentContentModel::columnCount([[maybe_unused]] const QModelIndex &parent) return TorrentContentModelItem::NB_COL; } +// Validates a file name, where "file" refers to both files and directories in Windows and Unix-like systems. +// Rejects empty or special names (".", ".."), platform-specific lengths or reserved names, and forbidden characters. +bool TorrentContentModel::isInvalidName(const QString &name) const +{ + // Reject empty names or special directory names (".", "..") + if (name.isEmpty() || name == u"."_s || name == u".."_s) + return true; + + #ifdef Q_OS_WIN + // Windows restricts file names to 255 characters and prohibits trailing dots + if (name.length() > 255 || name.endsWith(u'.')) + return true; + #else + // Non-Windows systems limit file name lengths to 255 bytes in UTF-8 encoding + if (name.toUtf8().length() > 255) + return true; + #endif + + #ifdef Q_OS_WIN + // Windows reserves certain names for devices, which cannot be used as file names + static const QStringList reservedNames { + QStringLiteral("CON"), QStringLiteral("PRN"), QStringLiteral("AUX"), QStringLiteral("NUL"), + QStringLiteral("COM1"), QStringLiteral("COM2"), QStringLiteral("COM3"), QStringLiteral("COM4"), + QStringLiteral("COM5"), QStringLiteral("COM6"), QStringLiteral("COM7"), QStringLiteral("COM8"), + QStringLiteral("COM9"), QStringLiteral("COM¹"), QStringLiteral("COM²"), QStringLiteral("COM³"), + QStringLiteral("LPT1"), QStringLiteral("LPT2"), QStringLiteral("LPT3"), QStringLiteral("LPT4"), + QStringLiteral("LPT5"), QStringLiteral("LPT6"), QStringLiteral("LPT7"), QStringLiteral("LPT8"), + QStringLiteral("LPT9"), QStringLiteral("LPT¹"), QStringLiteral("LPT²"), QStringLiteral("LPT³") + }; + const QString baseName = name.section(u'.', 0, 0); + if (reservedNames.contains(baseName, Qt::CaseInsensitive)) + return true; + #endif + + // Check for control characters, delete character and forward slash + for (const QChar &c : name) + { + const ushort unicode = c.unicode(); + if (unicode < 32 || unicode == 127 || c == u'/') + return true; + #ifdef Q_OS_WIN + // Windows forbids reserved characters in file names + if (c == u'\\' || c == u'<' || c == u'>' || c == u':' || c == u'"' || + c == u'|' || c == u'?' || c == u'*') + return true; + #endif + } + return false; +} + bool TorrentContentModel::setData(const QModelIndex &index, const QVariant &value, const int role) { if (!index.isValid()) @@ -301,28 +349,16 @@ bool TorrentContentModel::setData(const QModelIndex &index, const QVariant &valu case TorrentContentModelItem::COL_NAME: { const QString currentName = item->name(); - QString newName = value.toString().trimmed(); - + const QString newName = value.toString().trimmed(); + if (currentName != newName) { - bool invalid = newName.isEmpty() || (newName == u"."_s) || (newName == u".."_s) || (newName.length() > 255) || newName.endsWith(u'.'); - if (!invalid) - { - for (const QChar &c : newName) - { - const ushort unicode = c.unicode(); - if ((unicode < 32) || (unicode == 127) || (c == u'<') || (c == u'>') || (c == u':') || (c == u'"') || (c == u'/') || (c == u'\\') || (c == u'|') || (c == u'?') || (c == u'*')) - { - invalid = true; - break; - } - } - } - if (invalid) + if (isInvalidName(newName)) { emit renameFailed(tr("The name \"%1\" is invalid.").arg(newName)); return false; } + try { const Path parentPath = getItemPath(index.parent()); diff --git a/src/gui/torrentcontentmodel.h b/src/gui/torrentcontentmodel.h index b1b331052..9895dfd33 100644 --- a/src/gui/torrentcontentmodel.h +++ b/src/gui/torrentcontentmodel.h @@ -94,6 +94,7 @@ private: void updateFilesPriorities(); void updateFilesAvailability(); bool setItemPriority(const QModelIndex &index, BitTorrent::DownloadPriority priority); + bool isInvalidName(const QString &name) const; void notifySubtreeUpdated(const QModelIndex &index, const QList &columns); BitTorrent::TorrentContentHandler *m_contentHandler = nullptr;