mirror of
https://github.com/qbittorrent/qBittorrent.git
synced 2025-12-23 00:47:21 -06:00
Block invalid file names when renaming torrent content
Relevant issues: #22426, #21899,#16906,#16834, #16271, #11276, #9085, #11340, #14728, #20412, #20337, #17049, #17752, #17469, #15378, #15227, #14908. PR #23060. --------- Co-authored-by: Chocobo1 <Chocobo1@users.noreply.github.com>
This commit is contained in:
@@ -219,6 +219,60 @@ Path Utils::Fs::tempPath()
|
|||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validates a file name, where "file" refers to both files and directories in Windows and Unix-like systems.
|
||||||
|
// Returns true if the name is valid, false if it contains empty/special names, exceeds platform-specific lengths,
|
||||||
|
// uses reserved names, or includes forbidden characters.
|
||||||
|
bool Utils::Fs::isValidName(const QString &name)
|
||||||
|
{
|
||||||
|
// Reject empty names or special directory names (".", "..")
|
||||||
|
if (name.isEmpty() || (name == u"."_s) || (name == u".."_s))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
#ifdef Q_OS_WIN
|
||||||
|
// Windows restricts file names to 255 characters and prohibits trailing dots
|
||||||
|
if ((name.length() > 255) || name.endsWith(u'.'))
|
||||||
|
return false;
|
||||||
|
#else
|
||||||
|
// Non-Windows systems limit file name lengths to 255 bytes in UTF-8 encoding
|
||||||
|
if (name.toUtf8().length() > 255)
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef Q_OS_WIN
|
||||||
|
// Windows reserves certain names for devices, which cannot be used as file names
|
||||||
|
const QSet reservedNames
|
||||||
|
{
|
||||||
|
u"CON"_s, u"PRN"_s, u"AUX"_s, u"NUL"_s,
|
||||||
|
u"COM1"_s, u"COM2"_s, u"COM3"_s, u"COM4"_s,
|
||||||
|
u"COM5"_s, u"COM6"_s, u"COM7"_s, u"COM8"_s,
|
||||||
|
u"COM9"_s, u"COM¹"_s, u"COM²"_s, u"COM³"_s,
|
||||||
|
u"LPT1"_s, u"LPT2"_s, u"LPT3"_s, u"LPT4"_s,
|
||||||
|
u"LPT5"_s, u"LPT6"_s, u"LPT7"_s, u"LPT8"_s,
|
||||||
|
u"LPT9"_s, u"LPT¹"_s, u"LPT²"_s, u"LPT³"_s
|
||||||
|
};
|
||||||
|
const QString baseName = name.section(u'.', 0, 0).toUpper();
|
||||||
|
if (reservedNames.contains(baseName))
|
||||||
|
return false;
|
||||||
|
#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 false;
|
||||||
|
#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 false;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
// If none of the invalid conditions are met, the name is valid
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool Utils::Fs::isRegularFile(const Path &path)
|
bool Utils::Fs::isRegularFile(const Path &path)
|
||||||
{
|
{
|
||||||
std::error_code ec;
|
std::error_code ec;
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ namespace Utils::Fs
|
|||||||
qint64 computePathSize(const Path &path);
|
qint64 computePathSize(const Path &path);
|
||||||
qint64 freeDiskSpaceOnPath(const Path &path);
|
qint64 freeDiskSpaceOnPath(const Path &path);
|
||||||
|
|
||||||
|
bool isValidName(const QString &name);
|
||||||
bool isRegularFile(const Path &path);
|
bool isRegularFile(const Path &path);
|
||||||
bool isDir(const Path &path);
|
bool isDir(const Path &path);
|
||||||
bool isReadable(const Path &path);
|
bool isReadable(const Path &path);
|
||||||
|
|||||||
@@ -299,9 +299,16 @@ bool TorrentContentModel::setData(const QModelIndex &index, const QVariant &valu
|
|||||||
case TorrentContentModelItem::COL_NAME:
|
case TorrentContentModelItem::COL_NAME:
|
||||||
{
|
{
|
||||||
const QString currentName = item->name();
|
const QString currentName = item->name();
|
||||||
const QString newName = value.toString();
|
const QString newName = value.toString().trimmed();
|
||||||
|
|
||||||
if (currentName != newName)
|
if (currentName != newName)
|
||||||
{
|
{
|
||||||
|
if (!Utils::Fs::isValidName(newName))
|
||||||
|
{
|
||||||
|
emit renameFailed(tr("The name is invalid: \"%1\"").arg(newName));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
const Path parentPath = getItemPath(index.parent());
|
const Path parentPath = getItemPath(index.parent());
|
||||||
|
|||||||
@@ -34,6 +34,7 @@
|
|||||||
#include <functional>
|
#include <functional>
|
||||||
|
|
||||||
#include <QBitArray>
|
#include <QBitArray>
|
||||||
|
#include <QFileInfo>
|
||||||
#include <QFuture>
|
#include <QFuture>
|
||||||
#include <QJsonArray>
|
#include <QJsonArray>
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
@@ -1990,6 +1991,10 @@ void TorrentsController::renameFileAction()
|
|||||||
{
|
{
|
||||||
requireParams({u"hash"_s, u"oldPath"_s, u"newPath"_s});
|
requireParams({u"hash"_s, u"oldPath"_s, u"newPath"_s});
|
||||||
|
|
||||||
|
const QString newFileName = QFileInfo(params()[u"newPath"_s]).fileName();
|
||||||
|
if (!Utils::Fs::isValidName(newFileName))
|
||||||
|
throw APIError(APIErrorType::Conflict, tr("File name has invalid characters"));
|
||||||
|
|
||||||
const auto id = BitTorrent::TorrentID::fromString(params()[u"hash"_s]);
|
const auto id = BitTorrent::TorrentID::fromString(params()[u"hash"_s]);
|
||||||
BitTorrent::Torrent *const torrent = BitTorrent::Session::instance()->getTorrent(id);
|
BitTorrent::Torrent *const torrent = BitTorrent::Session::instance()->getTorrent(id);
|
||||||
if (!torrent)
|
if (!torrent)
|
||||||
@@ -2014,6 +2019,10 @@ void TorrentsController::renameFolderAction()
|
|||||||
{
|
{
|
||||||
requireParams({u"hash"_s, u"oldPath"_s, u"newPath"_s});
|
requireParams({u"hash"_s, u"oldPath"_s, u"newPath"_s});
|
||||||
|
|
||||||
|
const QString newFolderName = QFileInfo(params()[u"newPath"_s]).fileName();
|
||||||
|
if (!Utils::Fs::isValidName(newFolderName))
|
||||||
|
throw APIError(APIErrorType::Conflict, tr("Folder name has invalid characters"));
|
||||||
|
|
||||||
const auto id = BitTorrent::TorrentID::fromString(params()[u"hash"_s]);
|
const auto id = BitTorrent::TorrentID::fromString(params()[u"hash"_s]);
|
||||||
BitTorrent::Torrent *const torrent = BitTorrent::Session::instance()->getTorrent(id);
|
BitTorrent::Torrent *const torrent = BitTorrent::Session::instance()->getTorrent(id);
|
||||||
if (!torrent)
|
if (!torrent)
|
||||||
|
|||||||
Reference in New Issue
Block a user