diff --git a/src/gui/torrentcontentmodel.cpp b/src/gui/torrentcontentmodel.cpp index 2df7b8176..734a4c0b5 100644 --- a/src/gui/torrentcontentmodel.cpp +++ b/src/gui/torrentcontentmodel.cpp @@ -273,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()) @@ -299,9 +349,16 @@ bool TorrentContentModel::setData(const QModelIndex &index, const QVariant &valu case TorrentContentModelItem::COL_NAME: { const QString currentName = item->name(); - const QString newName = value.toString(); + const QString newName = value.toString().trimmed(); + if (currentName != newName) { + 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;