mirror of
https://github.com/qbittorrent/qBittorrent
synced 2025-07-06 05:01:25 -07:00
Don't read unlimited data from files
It now guards against reading infinite files such as `/dev/zero`. And most readings are bound with a (lax) limit. As a side effect, more checking are done when reading a file and overall the reading procedure is more robust. PR #19095.
This commit is contained in:
parent
81bc910d68
commit
79ca2e145f
24 changed files with 370 additions and 199 deletions
|
@ -36,6 +36,7 @@
|
||||||
|
|
||||||
#include <QByteArray>
|
#include <QByteArray>
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
|
#include <QFile>
|
||||||
#include <QRegularExpression>
|
#include <QRegularExpression>
|
||||||
#include <QThread>
|
#include <QThread>
|
||||||
|
|
||||||
|
@ -133,17 +134,19 @@ BitTorrent::LoadResumeDataResult BitTorrent::BencodeResumeDataStorage::load(cons
|
||||||
const Path fastresumePath = path() / Path(idString + u".fastresume");
|
const Path fastresumePath = path() / Path(idString + u".fastresume");
|
||||||
const Path torrentFilePath = path() / Path(idString + u".torrent");
|
const Path torrentFilePath = path() / Path(idString + u".torrent");
|
||||||
|
|
||||||
QFile resumeDataFile {fastresumePath.data()};
|
const auto resumeDataReadResult = Utils::IO::readFile(fastresumePath, MAX_TORRENT_SIZE);
|
||||||
if (!resumeDataFile.open(QIODevice::ReadOnly))
|
if (!resumeDataReadResult)
|
||||||
return nonstd::make_unexpected(tr("Cannot read file %1: %2").arg(fastresumePath.toString(), resumeDataFile.errorString()));
|
return nonstd::make_unexpected(resumeDataReadResult.error().message);
|
||||||
|
|
||||||
QFile metadataFile {torrentFilePath.data()};
|
const auto metadataReadResult = Utils::IO::readFile(torrentFilePath, MAX_TORRENT_SIZE);
|
||||||
if (metadataFile.exists() && !metadataFile.open(QIODevice::ReadOnly))
|
if (!metadataReadResult)
|
||||||
return nonstd::make_unexpected(tr("Cannot read file %1: %2").arg(torrentFilePath.toString(), metadataFile.errorString()));
|
{
|
||||||
|
if (metadataReadResult.error().status != Utils::IO::ReadError::NotExist)
|
||||||
const QByteArray data = resumeDataFile.readAll();
|
return nonstd::make_unexpected(metadataReadResult.error().message);
|
||||||
const QByteArray metadata = (metadataFile.isOpen() ? metadataFile.readAll() : "");
|
}
|
||||||
|
|
||||||
|
const QByteArray data = resumeDataReadResult.value();
|
||||||
|
const QByteArray metadata = metadataReadResult.value_or(QByteArray());
|
||||||
return loadTorrentResumeData(data, metadata);
|
return loadTorrentResumeData(data, metadata);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -161,6 +164,8 @@ void BitTorrent::BencodeResumeDataStorage::doLoadAll() const
|
||||||
|
|
||||||
void BitTorrent::BencodeResumeDataStorage::loadQueue(const Path &queueFilename)
|
void BitTorrent::BencodeResumeDataStorage::loadQueue(const Path &queueFilename)
|
||||||
{
|
{
|
||||||
|
const int lineMaxLength = 48;
|
||||||
|
|
||||||
QFile queueFile {queueFilename.data()};
|
QFile queueFile {queueFilename.data()};
|
||||||
if (!queueFile.exists())
|
if (!queueFile.exists())
|
||||||
return;
|
return;
|
||||||
|
@ -175,7 +180,7 @@ void BitTorrent::BencodeResumeDataStorage::loadQueue(const Path &queueFilename)
|
||||||
int start = 0;
|
int start = 0;
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
const auto line = QString::fromLatin1(queueFile.readLine().trimmed());
|
const auto line = QString::fromLatin1(queueFile.readLine(lineMaxLength).trimmed());
|
||||||
if (line.isEmpty())
|
if (line.isEmpty())
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|
|
@ -41,7 +41,6 @@
|
||||||
|
|
||||||
#include <QByteArray>
|
#include <QByteArray>
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QFile>
|
|
||||||
#include <QMutex>
|
#include <QMutex>
|
||||||
#include <QSet>
|
#include <QSet>
|
||||||
#include <QSqlDatabase>
|
#include <QSqlDatabase>
|
||||||
|
|
|
@ -60,7 +60,6 @@
|
||||||
|
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
#include <QFile>
|
|
||||||
#include <QHostAddress>
|
#include <QHostAddress>
|
||||||
#include <QJsonArray>
|
#include <QJsonArray>
|
||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
|
@ -5101,8 +5100,8 @@ void SessionImpl::loadCategories()
|
||||||
{
|
{
|
||||||
m_categories.clear();
|
m_categories.clear();
|
||||||
|
|
||||||
QFile confFile {(specialFolderLocation(SpecialFolder::Config) / CATEGORIES_FILE_NAME).data()};
|
const Path path = specialFolderLocation(SpecialFolder::Config) / CATEGORIES_FILE_NAME;
|
||||||
if (!confFile.exists())
|
if (!path.exists())
|
||||||
{
|
{
|
||||||
// TODO: Remove the following upgrade code in v4.5
|
// TODO: Remove the following upgrade code in v4.5
|
||||||
// == BEGIN UPGRADE CODE ==
|
// == BEGIN UPGRADE CODE ==
|
||||||
|
@ -5113,26 +5112,27 @@ void SessionImpl::loadCategories()
|
||||||
// return;
|
// return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!confFile.open(QFile::ReadOnly))
|
const int fileMaxSize = 1024 * 1024;
|
||||||
|
const auto readResult = Utils::IO::readFile(path, fileMaxSize);
|
||||||
|
if (!readResult)
|
||||||
{
|
{
|
||||||
LogMsg(tr("Failed to load Categories. File: \"%1\". Error: \"%2\"")
|
LogMsg(tr("Failed to load Categories. %1").arg(readResult.error().message), Log::WARNING);
|
||||||
.arg(confFile.fileName(), confFile.errorString()), Log::CRITICAL);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
QJsonParseError jsonError;
|
QJsonParseError jsonError;
|
||||||
const QJsonDocument jsonDoc = QJsonDocument::fromJson(confFile.readAll(), &jsonError);
|
const QJsonDocument jsonDoc = QJsonDocument::fromJson(readResult.value(), &jsonError);
|
||||||
if (jsonError.error != QJsonParseError::NoError)
|
if (jsonError.error != QJsonParseError::NoError)
|
||||||
{
|
{
|
||||||
LogMsg(tr("Failed to parse Categories configuration. File: \"%1\". Error: \"%2\"")
|
LogMsg(tr("Failed to parse Categories configuration. File: \"%1\". Error: \"%2\"")
|
||||||
.arg(confFile.fileName(), jsonError.errorString()), Log::WARNING);
|
.arg(path.toString(), jsonError.errorString()), Log::WARNING);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!jsonDoc.isObject())
|
if (!jsonDoc.isObject())
|
||||||
{
|
{
|
||||||
LogMsg(tr("Failed to load Categories configuration. File: \"%1\". Reason: invalid data format")
|
LogMsg(tr("Failed to load Categories configuration. File: \"%1\". Error: \"Invalid data format\"")
|
||||||
.arg(confFile.fileName()), Log::WARNING);
|
.arg(path.toString()), Log::WARNING);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -46,7 +46,6 @@
|
||||||
|
|
||||||
#include <QByteArray>
|
#include <QByteArray>
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QFile>
|
|
||||||
#include <QPointer>
|
#include <QPointer>
|
||||||
#include <QSet>
|
#include <QSet>
|
||||||
#include <QStringList>
|
#include <QStringList>
|
||||||
|
|
|
@ -103,28 +103,20 @@ nonstd::expected<TorrentInfo, QString> TorrentInfo::load(const QByteArray &data)
|
||||||
|
|
||||||
nonstd::expected<TorrentInfo, QString> TorrentInfo::loadFromFile(const Path &path) noexcept
|
nonstd::expected<TorrentInfo, QString> TorrentInfo::loadFromFile(const Path &path) noexcept
|
||||||
{
|
{
|
||||||
QFile file {path.data()};
|
|
||||||
if (!file.open(QIODevice::ReadOnly))
|
|
||||||
return nonstd::make_unexpected(file.errorString());
|
|
||||||
|
|
||||||
if (file.size() > MAX_TORRENT_SIZE)
|
|
||||||
return nonstd::make_unexpected(tr("File size exceeds max limit %1").arg(Utils::Misc::friendlyUnit(MAX_TORRENT_SIZE)));
|
|
||||||
|
|
||||||
QByteArray data;
|
QByteArray data;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
data = file.readAll();
|
const auto readResult = Utils::IO::readFile(path, MAX_TORRENT_SIZE);
|
||||||
|
if (!readResult)
|
||||||
|
return nonstd::make_unexpected(readResult.error().message);
|
||||||
|
data = readResult.value();
|
||||||
}
|
}
|
||||||
catch (const std::bad_alloc &e)
|
catch (const std::bad_alloc &e)
|
||||||
{
|
{
|
||||||
return nonstd::make_unexpected(tr("Torrent file read error: %1").arg(QString::fromLocal8Bit(e.what())));
|
return nonstd::make_unexpected(tr("Failed to allocate memory when reading file. File: \"%1\". Error: \"%2\"")
|
||||||
|
.arg(path.toString(), QString::fromLocal8Bit(e.what())));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.size() != file.size())
|
|
||||||
return nonstd::make_unexpected(tr("Torrent file read error: size mismatch"));
|
|
||||||
|
|
||||||
file.close();
|
|
||||||
|
|
||||||
return load(data);
|
return load(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -31,7 +31,6 @@
|
||||||
|
|
||||||
#include "feed_serializer.h"
|
#include "feed_serializer.h"
|
||||||
|
|
||||||
#include <QFile>
|
|
||||||
#include <QJsonArray>
|
#include <QJsonArray>
|
||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
|
@ -46,23 +45,21 @@ const int ARTICLEDATALIST_TYPEID = qRegisterMetaType<QVector<QVariantHash>>();
|
||||||
|
|
||||||
void RSS::Private::FeedSerializer::load(const Path &dataFileName, const QString &url)
|
void RSS::Private::FeedSerializer::load(const Path &dataFileName, const QString &url)
|
||||||
{
|
{
|
||||||
QFile file {dataFileName.data()};
|
const int fileMaxSize = 10 * 1024 * 1024;
|
||||||
|
const auto readResult = Utils::IO::readFile(dataFileName, fileMaxSize);
|
||||||
|
if (!readResult)
|
||||||
|
{
|
||||||
|
if (readResult.error().status == Utils::IO::ReadError::NotExist)
|
||||||
|
{
|
||||||
|
emit loadingFinished({});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!file.exists())
|
LogMsg(tr("Failed to read RSS session data. %1").arg(readResult.error().message), Log::WARNING);
|
||||||
{
|
return;
|
||||||
emit loadingFinished({});
|
|
||||||
}
|
|
||||||
else if (file.open(QFile::ReadOnly))
|
|
||||||
{
|
|
||||||
emit loadingFinished(loadArticles(file.readAll(), url));
|
|
||||||
file.close();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
LogMsg(tr("Couldn't read RSS Session data from %1. Error: %2")
|
|
||||||
.arg(dataFileName.toString(), file.errorString())
|
|
||||||
, Log::WARNING);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
emit loadingFinished(loadArticles(readResult.value(), url));
|
||||||
}
|
}
|
||||||
|
|
||||||
void RSS::Private::FeedSerializer::store(const Path &dataFileName, const QVector<QVariantHash> &articlesData)
|
void RSS::Private::FeedSerializer::store(const Path &dataFileName, const QVector<QVariantHash> &articlesData)
|
||||||
|
|
|
@ -48,6 +48,7 @@
|
||||||
#include "../logger.h"
|
#include "../logger.h"
|
||||||
#include "../profile.h"
|
#include "../profile.h"
|
||||||
#include "../utils/fs.h"
|
#include "../utils/fs.h"
|
||||||
|
#include "../utils/io.h"
|
||||||
#include "rss_article.h"
|
#include "rss_article.h"
|
||||||
#include "rss_autodownloadrule.h"
|
#include "rss_autodownloadrule.h"
|
||||||
#include "rss_feed.h"
|
#include "rss_feed.h"
|
||||||
|
@ -493,21 +494,21 @@ void AutoDownloader::processJob(const QSharedPointer<ProcessingJob> &job)
|
||||||
|
|
||||||
void AutoDownloader::load()
|
void AutoDownloader::load()
|
||||||
{
|
{
|
||||||
QFile rulesFile {(m_fileStorage->storageDir() / Path(RULES_FILE_NAME)).data()};
|
const qint64 maxFileSize = 10 * 1024 * 1024;
|
||||||
|
const auto readResult = Utils::IO::readFile((m_fileStorage->storageDir() / Path(RULES_FILE_NAME)), maxFileSize);
|
||||||
|
if (!readResult)
|
||||||
|
{
|
||||||
|
if (readResult.error().status == Utils::IO::ReadError::NotExist)
|
||||||
|
{
|
||||||
|
loadRulesLegacy();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!rulesFile.exists())
|
LogMsg((tr("Failed to read RSS AutoDownloader rules. %1").arg(readResult.error().message)), Log::WARNING);
|
||||||
{
|
return;
|
||||||
loadRulesLegacy();
|
|
||||||
}
|
|
||||||
else if (rulesFile.open(QFile::ReadOnly))
|
|
||||||
{
|
|
||||||
loadRules(rulesFile.readAll());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
LogMsg(tr("Couldn't read RSS AutoDownloader rules from %1. Error: %2")
|
|
||||||
.arg(rulesFile.fileName(), rulesFile.errorString()), Log::CRITICAL);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
loadRules(readResult.value());
|
||||||
}
|
}
|
||||||
|
|
||||||
void AutoDownloader::loadRules(const QByteArray &data)
|
void AutoDownloader::loadRules(const QByteArray &data)
|
||||||
|
|
|
@ -45,6 +45,7 @@
|
||||||
#include "../profile.h"
|
#include "../profile.h"
|
||||||
#include "../settingsstorage.h"
|
#include "../settingsstorage.h"
|
||||||
#include "../utils/fs.h"
|
#include "../utils/fs.h"
|
||||||
|
#include "../utils/io.h"
|
||||||
#include "rss_article.h"
|
#include "rss_article.h"
|
||||||
#include "rss_feed.h"
|
#include "rss_feed.h"
|
||||||
#include "rss_folder.h"
|
#include "rss_folder.h"
|
||||||
|
@ -261,33 +262,35 @@ Item *Session::itemByPath(const QString &path) const
|
||||||
|
|
||||||
void Session::load()
|
void Session::load()
|
||||||
{
|
{
|
||||||
QFile itemsFile {(m_confFileStorage->storageDir() / Path(FEEDS_FILE_NAME)).data()};
|
const int fileMaxSize = 10 * 1024 * 1024;
|
||||||
if (!itemsFile.exists())
|
const Path path = m_confFileStorage->storageDir() / Path(FEEDS_FILE_NAME);
|
||||||
{
|
|
||||||
loadLegacy();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!itemsFile.open(QFile::ReadOnly))
|
const auto readResult = Utils::IO::readFile(path, fileMaxSize);
|
||||||
|
if (!readResult)
|
||||||
{
|
{
|
||||||
LogMsg(tr("Couldn't read RSS session data. File: \"%1\". Error: \"%2\"")
|
if (readResult.error().status == Utils::IO::ReadError::NotExist)
|
||||||
.arg(itemsFile.fileName(), itemsFile.errorString()), Log::WARNING);
|
{
|
||||||
|
loadLegacy();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LogMsg(tr("Failed to read RSS session data. %1").arg(readResult.error().message), Log::WARNING);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
QJsonParseError jsonError;
|
QJsonParseError jsonError;
|
||||||
const QJsonDocument jsonDoc = QJsonDocument::fromJson(itemsFile.readAll(), &jsonError);
|
const QJsonDocument jsonDoc = QJsonDocument::fromJson(readResult.value(), &jsonError);
|
||||||
if (jsonError.error != QJsonParseError::NoError)
|
if (jsonError.error != QJsonParseError::NoError)
|
||||||
{
|
{
|
||||||
LogMsg(tr("Couldn't parse RSS session data. File: \"%1\". Error: \"%2\"")
|
LogMsg(tr("Failed to parse RSS session data. File: \"%1\". Error: \"%2\"")
|
||||||
.arg(itemsFile.fileName(), jsonError.errorString()), Log::WARNING);
|
.arg(path.toString(), jsonError.errorString()), Log::WARNING);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!jsonDoc.isObject())
|
if (!jsonDoc.isObject())
|
||||||
{
|
{
|
||||||
LogMsg(tr("Couldn't load RSS session data. File: \"%1\". Error: Invalid data format.")
|
LogMsg(tr("Failed to load RSS session data. File: \"%1\". Error: \"Invalid data format.\"")
|
||||||
.arg(itemsFile.fileName()), Log::WARNING);
|
.arg(path.toString()), Log::WARNING);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -36,6 +36,7 @@
|
||||||
#include <QDomDocument>
|
#include <QDomDocument>
|
||||||
#include <QDomElement>
|
#include <QDomElement>
|
||||||
#include <QDomNode>
|
#include <QDomNode>
|
||||||
|
#include <QFile>
|
||||||
#include <QPointer>
|
#include <QPointer>
|
||||||
#include <QProcess>
|
#include <QProcess>
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
|
@ -517,7 +518,7 @@ void SearchPluginManager::update()
|
||||||
nova.start(Utils::ForeignApps::pythonInfo().executableName, params, QIODevice::ReadOnly);
|
nova.start(Utils::ForeignApps::pythonInfo().executableName, params, QIODevice::ReadOnly);
|
||||||
nova.waitForFinished();
|
nova.waitForFinished();
|
||||||
|
|
||||||
const auto capabilities = QString::fromUtf8(nova.readAll());
|
const auto capabilities = QString::fromUtf8(nova.readAllStandardOutput());
|
||||||
QDomDocument xmlDoc;
|
QDomDocument xmlDoc;
|
||||||
if (!xmlDoc.setContent(capabilities))
|
if (!xmlDoc.setContent(capabilities))
|
||||||
{
|
{
|
||||||
|
@ -629,13 +630,15 @@ Path SearchPluginManager::pluginPath(const QString &name)
|
||||||
|
|
||||||
PluginVersion SearchPluginManager::getPluginVersion(const Path &filePath)
|
PluginVersion SearchPluginManager::getPluginVersion(const Path &filePath)
|
||||||
{
|
{
|
||||||
|
const int lineMaxLength = 16;
|
||||||
|
|
||||||
QFile pluginFile {filePath.data()};
|
QFile pluginFile {filePath.data()};
|
||||||
if (!pluginFile.open(QIODevice::ReadOnly | QIODevice::Text))
|
if (!pluginFile.open(QIODevice::ReadOnly | QIODevice::Text))
|
||||||
return {};
|
return {};
|
||||||
|
|
||||||
while (!pluginFile.atEnd())
|
while (!pluginFile.atEnd())
|
||||||
{
|
{
|
||||||
const auto line = QString::fromUtf8(pluginFile.readLine()).remove(u' ');
|
const auto line = QString::fromUtf8(pluginFile.readLine(lineMaxLength)).remove(u' ');
|
||||||
if (!line.startsWith(u"#VERSION:", Qt::CaseInsensitive)) continue;
|
if (!line.startsWith(u"#VERSION:", Qt::CaseInsensitive)) continue;
|
||||||
|
|
||||||
const QString versionStr = line.mid(9);
|
const QString versionStr = line.mid(9);
|
||||||
|
|
|
@ -177,33 +177,35 @@ void TorrentFilesWatcher::initWorker()
|
||||||
|
|
||||||
void TorrentFilesWatcher::load()
|
void TorrentFilesWatcher::load()
|
||||||
{
|
{
|
||||||
QFile confFile {(specialFolderLocation(SpecialFolder::Config) / Path(CONF_FILE_NAME)).data()};
|
const int fileMaxSize = 10 * 1024 * 1024;
|
||||||
if (!confFile.exists())
|
const Path path = specialFolderLocation(SpecialFolder::Config) / Path(CONF_FILE_NAME);
|
||||||
{
|
|
||||||
loadLegacy();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!confFile.open(QFile::ReadOnly))
|
const auto readResult = Utils::IO::readFile(path, fileMaxSize);
|
||||||
|
if (!readResult)
|
||||||
{
|
{
|
||||||
LogMsg(tr("Couldn't load Watched Folders configuration from %1. Error: %2")
|
if (readResult.error().status == Utils::IO::ReadError::NotExist)
|
||||||
.arg(confFile.fileName(), confFile.errorString()), Log::WARNING);
|
{
|
||||||
|
loadLegacy();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LogMsg(tr("Failed to load Watched Folders configuration. %1").arg(readResult.error().message), Log::WARNING);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
QJsonParseError jsonError;
|
QJsonParseError jsonError;
|
||||||
const QJsonDocument jsonDoc = QJsonDocument::fromJson(confFile.readAll(), &jsonError);
|
const QJsonDocument jsonDoc = QJsonDocument::fromJson(readResult.value(), &jsonError);
|
||||||
if (jsonError.error != QJsonParseError::NoError)
|
if (jsonError.error != QJsonParseError::NoError)
|
||||||
{
|
{
|
||||||
LogMsg(tr("Couldn't parse Watched Folders configuration from %1. Error: %2")
|
LogMsg(tr("Failed to parse Watched Folders configuration from %1. Error: \"%2\"")
|
||||||
.arg(confFile.fileName(), jsonError.errorString()), Log::WARNING);
|
.arg(path.toString(), jsonError.errorString()), Log::WARNING);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!jsonDoc.isObject())
|
if (!jsonDoc.isObject())
|
||||||
{
|
{
|
||||||
LogMsg(tr("Couldn't load Watched Folders configuration from %1. Invalid data format.")
|
LogMsg(tr("Failed to load Watched Folders configuration from %1. Error: \"Invalid data format.\"")
|
||||||
.arg(confFile.fileName()), Log::WARNING);
|
.arg(path.toString()), Log::WARNING);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -426,17 +428,26 @@ void TorrentFilesWatcher::Worker::processFolder(const Path &path, const Path &wa
|
||||||
|
|
||||||
if (filePath.hasExtension(u".magnet"_qs))
|
if (filePath.hasExtension(u".magnet"_qs))
|
||||||
{
|
{
|
||||||
|
const int fileMaxSize = 100 * 1024 * 1024;
|
||||||
|
|
||||||
QFile file {filePath.data()};
|
QFile file {filePath.data()};
|
||||||
if (file.open(QIODevice::ReadOnly | QIODevice::Text))
|
if (file.open(QIODevice::ReadOnly | QIODevice::Text))
|
||||||
{
|
{
|
||||||
while (!file.atEnd())
|
if (file.size() <= fileMaxSize)
|
||||||
{
|
{
|
||||||
const auto line = QString::fromLatin1(file.readLine()).trimmed();
|
while (!file.atEnd())
|
||||||
emit magnetFound(BitTorrent::MagnetUri(line), addTorrentParams);
|
{
|
||||||
}
|
const auto line = QString::fromLatin1(file.readLine()).trimmed();
|
||||||
|
emit magnetFound(BitTorrent::MagnetUri(line), addTorrentParams);
|
||||||
|
}
|
||||||
|
|
||||||
file.close();
|
file.close();
|
||||||
Utils::Fs::removeFile(filePath);
|
Utils::Fs::removeFile(filePath);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LogMsg(tr("Magnet file too big. File: %1").arg(file.errorString()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
|
@ -31,7 +31,9 @@
|
||||||
#include <libtorrent/bencode.hpp>
|
#include <libtorrent/bencode.hpp>
|
||||||
#include <libtorrent/entry.hpp>
|
#include <libtorrent/entry.hpp>
|
||||||
|
|
||||||
|
#include <QCoreApplication>
|
||||||
#include <QByteArray>
|
#include <QByteArray>
|
||||||
|
#include <QFile>
|
||||||
#include <QFileDevice>
|
#include <QFileDevice>
|
||||||
#include <QSaveFile>
|
#include <QSaveFile>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
@ -69,6 +71,36 @@ Utils::IO::FileDeviceOutputIterator &Utils::IO::FileDeviceOutputIterator::operat
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nonstd::expected<QByteArray, Utils::IO::ReadError> Utils::IO::readFile(const Path &path, const qint64 maxSize, const QIODevice::OpenMode additionalMode)
|
||||||
|
{
|
||||||
|
QFile file {path.data()};
|
||||||
|
if (!file.open(QIODevice::ReadOnly | additionalMode))
|
||||||
|
{
|
||||||
|
const QString message = QCoreApplication::translate("Utils::IO", "File open error. File: \"%1\". Error: \"%2\"")
|
||||||
|
.arg(file.fileName(), file.errorString());
|
||||||
|
return nonstd::make_unexpected(ReadError {ReadError::NotExist, message});
|
||||||
|
}
|
||||||
|
|
||||||
|
const qint64 fileSize = file.size();
|
||||||
|
if ((maxSize >= 0) && (fileSize > maxSize))
|
||||||
|
{
|
||||||
|
const QString message = QCoreApplication::translate("Utils::IO", "File size exceeds limit. File: \"%1\". File size: %2. Size limit: %3")
|
||||||
|
.arg(file.fileName(), QString::number(fileSize), QString::number(maxSize));
|
||||||
|
return nonstd::make_unexpected(ReadError {ReadError::ExceedSize, message});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do not use `QIODevice::readAll()` it won't stop when reading `/dev/zero`
|
||||||
|
const QByteArray data = file.read(fileSize);
|
||||||
|
if (const qint64 dataSize = data.size(); dataSize != fileSize)
|
||||||
|
{
|
||||||
|
const QString message = QCoreApplication::translate("Utils::IO", "Read size mismatch. File: \"%1\". Expected: %2. Actual: %3")
|
||||||
|
.arg(file.fileName(), QString::number(fileSize), QString::number(dataSize));
|
||||||
|
return nonstd::make_unexpected(ReadError {ReadError::SizeMismatch, message});
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
nonstd::expected<void, QString> Utils::IO::saveToFile(const Path &path, const QByteArray &data)
|
nonstd::expected<void, QString> Utils::IO::saveToFile(const Path &path, const QByteArray &data)
|
||||||
{
|
{
|
||||||
if (const Path parentPath = path.parentPath(); !parentPath.isEmpty())
|
if (const Path parentPath = path.parentPath(); !parentPath.isEmpty())
|
||||||
|
|
|
@ -33,6 +33,8 @@
|
||||||
|
|
||||||
#include <libtorrent/fwd.hpp>
|
#include <libtorrent/fwd.hpp>
|
||||||
|
|
||||||
|
#include <QIODevice>
|
||||||
|
|
||||||
#include "base/3rdparty/expected.hpp"
|
#include "base/3rdparty/expected.hpp"
|
||||||
#include "base/pathfwd.h"
|
#include "base/pathfwd.h"
|
||||||
|
|
||||||
|
@ -81,6 +83,23 @@ namespace Utils::IO
|
||||||
int m_bufferSize = 0;
|
int m_bufferSize = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct ReadError
|
||||||
|
{
|
||||||
|
enum Code
|
||||||
|
{
|
||||||
|
NotExist,
|
||||||
|
ExceedSize,
|
||||||
|
SizeMismatch
|
||||||
|
};
|
||||||
|
|
||||||
|
Code status = {};
|
||||||
|
QString message;
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: define a specific type for `additionalMode`
|
||||||
|
// providing `size` is explicit and is strongly recommended
|
||||||
|
nonstd::expected<QByteArray, ReadError> readFile(const Path &path, qint64 size, QIODevice::OpenMode additionalMode = {});
|
||||||
|
|
||||||
nonstd::expected<void, QString> saveToFile(const Path &path, const QByteArray &data);
|
nonstd::expected<void, QString> saveToFile(const Path &path, const QByteArray &data);
|
||||||
nonstd::expected<void, QString> saveToFile(const Path &path, const lt::entry &data);
|
nonstd::expected<void, QString> saveToFile(const Path &path, const lt::entry &data);
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,11 +28,10 @@
|
||||||
|
|
||||||
#include "aboutdialog.h"
|
#include "aboutdialog.h"
|
||||||
|
|
||||||
#include <QFile>
|
|
||||||
|
|
||||||
#include "base/global.h"
|
#include "base/global.h"
|
||||||
#include "base/path.h"
|
#include "base/path.h"
|
||||||
#include "base/unicodestrings.h"
|
#include "base/unicodestrings.h"
|
||||||
|
#include "base/utils/io.h"
|
||||||
#include "base/utils/misc.h"
|
#include "base/utils/misc.h"
|
||||||
#include "base/version.h"
|
#include "base/version.h"
|
||||||
#include "ui_aboutdialog.h"
|
#include "ui_aboutdialog.h"
|
||||||
|
@ -75,27 +74,24 @@ AboutDialog::AboutDialog(QWidget *parent)
|
||||||
m_ui->labelMascot->setPixmap(Utils::Gui::scaledPixmap(Path(u":/icons/mascot.png"_qs), this));
|
m_ui->labelMascot->setPixmap(Utils::Gui::scaledPixmap(Path(u":/icons/mascot.png"_qs), this));
|
||||||
|
|
||||||
// Thanks
|
// Thanks
|
||||||
QFile thanksfile(u":/thanks.html"_qs);
|
if (const auto readResult = Utils::IO::readFile(Path(u":/thanks.html"_qs), -1, QIODevice::Text)
|
||||||
if (thanksfile.open(QIODevice::ReadOnly | QIODevice::Text))
|
; readResult)
|
||||||
{
|
{
|
||||||
m_ui->textBrowserThanks->setHtml(QString::fromUtf8(thanksfile.readAll().constData()));
|
m_ui->textBrowserThanks->setHtml(QString::fromUtf8(readResult.value()));
|
||||||
thanksfile.close();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Translation
|
// Translation
|
||||||
QFile translatorsfile(u":/translators.html"_qs);
|
if (const auto readResult = Utils::IO::readFile(Path(u":/translators.html"_qs), -1, QIODevice::Text)
|
||||||
if (translatorsfile.open(QIODevice::ReadOnly | QIODevice::Text))
|
; readResult)
|
||||||
{
|
{
|
||||||
m_ui->textBrowserTranslation->setHtml(QString::fromUtf8(translatorsfile.readAll().constData()));
|
m_ui->textBrowserTranslation->setHtml(QString::fromUtf8(readResult.value()));
|
||||||
translatorsfile.close();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// License
|
// License
|
||||||
QFile licensefile(u":/gpl.html"_qs);
|
if (const auto readResult = Utils::IO::readFile(Path(u":/gpl.html"_qs), -1, QIODevice::Text)
|
||||||
if (licensefile.open(QIODevice::ReadOnly | QIODevice::Text))
|
; readResult)
|
||||||
{
|
{
|
||||||
m_ui->textBrowserLicense->setHtml(QString::fromUtf8(licensefile.readAll().constData()));
|
m_ui->textBrowserLicense->setHtml(QString::fromUtf8(readResult.value()));
|
||||||
licensefile.close();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Software Used
|
// Software Used
|
||||||
|
|
|
@ -52,6 +52,7 @@
|
||||||
#include "base/rss/rss_session.h"
|
#include "base/rss/rss_session.h"
|
||||||
#include "base/torrentfileguard.h"
|
#include "base/torrentfileguard.h"
|
||||||
#include "base/torrentfileswatcher.h"
|
#include "base/torrentfileswatcher.h"
|
||||||
|
#include "base/utils/io.h"
|
||||||
#include "base/utils/misc.h"
|
#include "base/utils/misc.h"
|
||||||
#include "base/utils/net.h"
|
#include "base/utils/net.h"
|
||||||
#include "base/utils/password.h"
|
#include "base/utils/password.h"
|
||||||
|
@ -1751,46 +1752,22 @@ Path OptionsDialog::getFilter() const
|
||||||
#ifndef DISABLE_WEBUI
|
#ifndef DISABLE_WEBUI
|
||||||
void OptionsDialog::webUIHttpsCertChanged(const Path &path)
|
void OptionsDialog::webUIHttpsCertChanged(const Path &path)
|
||||||
{
|
{
|
||||||
const auto isCertFileValid = [&path]() -> bool
|
const auto readResult = Utils::IO::readFile(path, Utils::Net::MAX_SSL_FILE_SIZE);
|
||||||
{
|
const bool isCertValid = Utils::Net::isSSLCertificatesValid(readResult.value_or(QByteArray()));
|
||||||
if (path.isEmpty())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
QFile file {path.data()};
|
|
||||||
if (!file.open(QIODevice::ReadOnly))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (!Utils::Net::isSSLCertificatesValid(file.read(Utils::Net::MAX_SSL_FILE_SIZE)))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
m_ui->textWebUIHttpsCert->setSelectedPath(path);
|
m_ui->textWebUIHttpsCert->setSelectedPath(path);
|
||||||
m_ui->lblSslCertStatus->setPixmap(UIThemeManager::instance()->getScaledPixmap(
|
m_ui->lblSslCertStatus->setPixmap(UIThemeManager::instance()->getScaledPixmap(
|
||||||
(isCertFileValid() ? u"security-high"_qs : u"security-low"_qs), 24));
|
(isCertValid ? u"security-high"_qs : u"security-low"_qs), 24));
|
||||||
}
|
}
|
||||||
|
|
||||||
void OptionsDialog::webUIHttpsKeyChanged(const Path &path)
|
void OptionsDialog::webUIHttpsKeyChanged(const Path &path)
|
||||||
{
|
{
|
||||||
const auto isKeyFileValid = [&path]() -> bool
|
const auto readResult = Utils::IO::readFile(path, Utils::Net::MAX_SSL_FILE_SIZE);
|
||||||
{
|
const bool isKeyValid = Utils::Net::isSSLKeyValid(readResult.value_or(QByteArray()));
|
||||||
if (path.isEmpty())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
QFile file {path.data()};
|
|
||||||
if (!file.open(QIODevice::ReadOnly))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (!Utils::Net::isSSLKeyValid(file.read(Utils::Net::MAX_SSL_FILE_SIZE)))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
m_ui->textWebUIHttpsKey->setSelectedPath(path);
|
m_ui->textWebUIHttpsKey->setSelectedPath(path);
|
||||||
m_ui->lblSslKeyStatus->setPixmap(UIThemeManager::instance()->getScaledPixmap(
|
m_ui->lblSslKeyStatus->setPixmap(UIThemeManager::instance()->getScaledPixmap(
|
||||||
(isKeyFileValid() ? u"security-high"_qs : u"security-low"_qs), 24));
|
(isKeyValid ? u"security-high"_qs : u"security-low"_qs), 24));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool OptionsDialog::isWebUiEnabled() const
|
bool OptionsDialog::isWebUiEnabled() const
|
||||||
|
|
|
@ -30,7 +30,6 @@
|
||||||
|
|
||||||
#include <QAction>
|
#include <QAction>
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
#include <QFile>
|
|
||||||
#include <QHeaderView>
|
#include <QHeaderView>
|
||||||
#include <QMenu>
|
#include <QMenu>
|
||||||
#include <QMessageBox>
|
#include <QMessageBox>
|
||||||
|
|
|
@ -458,15 +458,16 @@ void AutomatedRssDownloader::onImportBtnClicked()
|
||||||
const Path path {QFileDialog::getOpenFileName(
|
const Path path {QFileDialog::getOpenFileName(
|
||||||
this, tr("Import RSS rules"), QDir::homePath()
|
this, tr("Import RSS rules"), QDir::homePath()
|
||||||
, u"%1;;%2"_qs.arg(m_formatFilterJSON, m_formatFilterLegacy), &selectedFilter)};
|
, u"%1;;%2"_qs.arg(m_formatFilterJSON, m_formatFilterLegacy), &selectedFilter)};
|
||||||
if (!path.exists())
|
|
||||||
return;
|
|
||||||
|
|
||||||
QFile file {path.data()};
|
const int fileMaxSize = 10 * 1024 * 1024;
|
||||||
if (!file.open(QIODevice::ReadOnly))
|
const auto readResult = Utils::IO::readFile(path, fileMaxSize);
|
||||||
|
if (!readResult)
|
||||||
{
|
{
|
||||||
QMessageBox::critical(
|
if (readResult.error().status == Utils::IO::ReadError::NotExist)
|
||||||
this, tr("I/O Error")
|
return;
|
||||||
, tr("Failed to open the file. Reason: %1").arg(file.errorString()));
|
|
||||||
|
QMessageBox::critical(this, tr("Import error")
|
||||||
|
, tr("Failed to read the file. %1").arg(readResult.error().message));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -479,13 +480,12 @@ void AutomatedRssDownloader::onImportBtnClicked()
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
RSS::AutoDownloader::instance()->importRules(file.readAll(),format);
|
RSS::AutoDownloader::instance()->importRules(readResult.value(), format);
|
||||||
}
|
}
|
||||||
catch (const RSS::ParsingError &error)
|
catch (const RSS::ParsingError &error)
|
||||||
{
|
{
|
||||||
QMessageBox::critical(
|
QMessageBox::critical(this, tr("Import error")
|
||||||
this, tr("Import Error")
|
, tr("Failed to import the selected rules file. Reason: %1").arg(error.message()));
|
||||||
, tr("Failed to import the selected rules file. Reason: %1").arg(error.message()));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -30,7 +30,6 @@
|
||||||
|
|
||||||
#include <QColor>
|
#include <QColor>
|
||||||
#include <QColorDialog>
|
#include <QColorDialog>
|
||||||
#include <QFile>
|
|
||||||
#include <QFileDialog>
|
#include <QFileDialog>
|
||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
|
|
|
@ -30,30 +30,17 @@
|
||||||
|
|
||||||
#include "uithemesource.h"
|
#include "uithemesource.h"
|
||||||
|
|
||||||
#include <QFile>
|
|
||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
|
|
||||||
#include "base/global.h"
|
#include "base/global.h"
|
||||||
#include "base/logger.h"
|
#include "base/logger.h"
|
||||||
#include "base/profile.h"
|
#include "base/profile.h"
|
||||||
|
#include "base/utils/io.h"
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
QByteArray readFile(const Path &filePath)
|
const qint64 FILE_MAX_SIZE = 1024 * 1024;
|
||||||
{
|
|
||||||
QFile file {filePath.data()};
|
|
||||||
if (!file.exists())
|
|
||||||
return {};
|
|
||||||
|
|
||||||
if (file.open(QIODevice::ReadOnly | QIODevice::Text))
|
|
||||||
return file.readAll();
|
|
||||||
|
|
||||||
LogMsg(UIThemeSource::tr("UITheme - Failed to open \"%1\". Reason: %2")
|
|
||||||
.arg(filePath.filename(), file.errorString())
|
|
||||||
, Log::WARNING);
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
QJsonObject parseThemeConfig(const QByteArray &data)
|
QJsonObject parseThemeConfig(const QByteArray &data)
|
||||||
{
|
{
|
||||||
|
@ -165,7 +152,16 @@ Path DefaultThemeSource::getIconPath(const QString &iconId, const ColorMode colo
|
||||||
|
|
||||||
void DefaultThemeSource::loadColors()
|
void DefaultThemeSource::loadColors()
|
||||||
{
|
{
|
||||||
const QByteArray configData = readFile(m_userPath / Path(CONFIG_FILE_NAME));
|
const auto readResult = Utils::IO::readFile((m_userPath / Path(CONFIG_FILE_NAME)), FILE_MAX_SIZE, QIODevice::Text);
|
||||||
|
if (!readResult)
|
||||||
|
{
|
||||||
|
if (readResult.error().status != Utils::IO::ReadError::NotExist)
|
||||||
|
LogMsg(tr("Failed to load default theme colors. %1").arg(readResult.error().message), Log::WARNING);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QByteArray configData = readResult.value();
|
||||||
if (configData.isEmpty())
|
if (configData.isEmpty())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
@ -233,7 +229,16 @@ Path CustomThemeSource::getIconPath(const QString &iconId, const ColorMode color
|
||||||
|
|
||||||
QByteArray CustomThemeSource::readStyleSheet()
|
QByteArray CustomThemeSource::readStyleSheet()
|
||||||
{
|
{
|
||||||
return readFile(themeRootPath() / Path(STYLESHEET_FILE_NAME));
|
const auto readResult = Utils::IO::readFile((themeRootPath() / Path(STYLESHEET_FILE_NAME)), FILE_MAX_SIZE, QIODevice::Text);
|
||||||
|
if (!readResult)
|
||||||
|
{
|
||||||
|
if (readResult.error().status != Utils::IO::ReadError::NotExist)
|
||||||
|
LogMsg(tr("Failed to load custom theme style sheet. %1").arg(readResult.error().message), Log::WARNING);
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
return readResult.value();
|
||||||
}
|
}
|
||||||
|
|
||||||
DefaultThemeSource *CustomThemeSource::defaultThemeSource() const
|
DefaultThemeSource *CustomThemeSource::defaultThemeSource() const
|
||||||
|
@ -243,7 +248,16 @@ DefaultThemeSource *CustomThemeSource::defaultThemeSource() const
|
||||||
|
|
||||||
void CustomThemeSource::loadColors()
|
void CustomThemeSource::loadColors()
|
||||||
{
|
{
|
||||||
const QByteArray configData = readFile(themeRootPath() / Path(CONFIG_FILE_NAME));
|
const auto readResult = Utils::IO::readFile((themeRootPath() / Path(CONFIG_FILE_NAME)), FILE_MAX_SIZE, QIODevice::Text);
|
||||||
|
if (!readResult)
|
||||||
|
{
|
||||||
|
if (readResult.error().status != Utils::IO::ReadError::NotExist)
|
||||||
|
LogMsg(tr("Failed to load custom theme colors. %1").arg(readResult.error().message), Log::WARNING);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QByteArray configData = readResult.value();
|
||||||
if (configData.isEmpty())
|
if (configData.isEmpty())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|
|
@ -177,10 +177,12 @@ void Utils::Gui::openFolderSelect(const Path &path)
|
||||||
QObject::connect(thread, &QThread::finished, thread, &QObject::deleteLater);
|
QObject::connect(thread, &QThread::finished, thread, &QObject::deleteLater);
|
||||||
thread->start();
|
thread->start();
|
||||||
#elif defined(Q_OS_UNIX) && !defined(Q_OS_MACOS)
|
#elif defined(Q_OS_UNIX) && !defined(Q_OS_MACOS)
|
||||||
|
const int lineMaxLength = 64;
|
||||||
|
|
||||||
QProcess proc;
|
QProcess proc;
|
||||||
proc.start(u"xdg-mime"_qs, {u"query"_qs, u"default"_qs, u"inode/directory"_qs});
|
proc.start(u"xdg-mime"_qs, {u"query"_qs, u"default"_qs, u"inode/directory"_qs});
|
||||||
proc.waitForFinished();
|
proc.waitForFinished();
|
||||||
const auto output = QString::fromLocal8Bit(proc.readLine().simplified());
|
const auto output = QString::fromLocal8Bit(proc.readLine(lineMaxLength).simplified());
|
||||||
if ((output == u"dolphin.desktop") || (output == u"org.kde.dolphin.desktop"))
|
if ((output == u"dolphin.desktop") || (output == u"org.kde.dolphin.desktop"))
|
||||||
{
|
{
|
||||||
proc.startDetached(u"dolphin"_qs, {u"--select"_qs, path.toString()});
|
proc.startDetached(u"dolphin"_qs, {u"--select"_qs, path.toString()});
|
||||||
|
@ -190,7 +192,7 @@ void Utils::Gui::openFolderSelect(const Path &path)
|
||||||
{
|
{
|
||||||
proc.start(u"nautilus"_qs, {u"--version"_qs});
|
proc.start(u"nautilus"_qs, {u"--version"_qs});
|
||||||
proc.waitForFinished();
|
proc.waitForFinished();
|
||||||
const auto nautilusVerStr = QString::fromLocal8Bit(proc.readLine()).remove(QRegularExpression(u"[^0-9.]"_qs));
|
const auto nautilusVerStr = QString::fromLocal8Bit(proc.readLine(lineMaxLength)).remove(QRegularExpression(u"[^0-9.]"_qs));
|
||||||
using NautilusVersion = Utils::Version<3>;
|
using NautilusVersion = Utils::Version<3>;
|
||||||
if (NautilusVersion::fromString(nautilusVerStr, {1, 0, 0}) > NautilusVersion(3, 28, 0))
|
if (NautilusVersion::fromString(nautilusVerStr, {1, 0, 0}) > NautilusVersion(3, 28, 0))
|
||||||
proc.startDetached(u"nautilus"_qs, {(Fs::isDir(path) ? path.parentPath() : path).toString()});
|
proc.startDetached(u"nautilus"_qs, {(Fs::isDir(path) ? path.parentPath() : path).toString()});
|
||||||
|
|
|
@ -33,7 +33,6 @@
|
||||||
#include <QDateTime>
|
#include <QDateTime>
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
#include <QFile>
|
|
||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
#include <QMimeDatabase>
|
#include <QMimeDatabase>
|
||||||
|
@ -48,6 +47,7 @@
|
||||||
#include "base/preferences.h"
|
#include "base/preferences.h"
|
||||||
#include "base/types.h"
|
#include "base/types.h"
|
||||||
#include "base/utils/fs.h"
|
#include "base/utils/fs.h"
|
||||||
|
#include "base/utils/io.h"
|
||||||
#include "base/utils/misc.h"
|
#include "base/utils/misc.h"
|
||||||
#include "base/utils/random.h"
|
#include "base/utils/random.h"
|
||||||
#include "base/utils/string.h"
|
#include "base/utils/string.h"
|
||||||
|
@ -496,23 +496,32 @@ void WebApplication::sendFile(const Path &path)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QFile file {path.data()};
|
const auto readResult = Utils::IO::readFile(path, MAX_ALLOWED_FILESIZE);
|
||||||
if (!file.open(QIODevice::ReadOnly))
|
if (!readResult)
|
||||||
{
|
{
|
||||||
qDebug("File %s was not found!", qUtf8Printable(path.toString()));
|
const QString message = tr("Web server error. %1").arg(readResult.error().message);
|
||||||
throw NotFoundHTTPError();
|
|
||||||
|
switch (readResult.error().status)
|
||||||
|
{
|
||||||
|
case Utils::IO::ReadError::NotExist:
|
||||||
|
qDebug("%s", qUtf8Printable(message));
|
||||||
|
// don't write log messages here to avoid exhausting the disk space
|
||||||
|
throw NotFoundHTTPError();
|
||||||
|
|
||||||
|
case Utils::IO::ReadError::ExceedSize:
|
||||||
|
qWarning("%s", qUtf8Printable(message));
|
||||||
|
LogMsg(message, Log::WARNING);
|
||||||
|
throw InternalServerErrorHTTPError(readResult.error().message);
|
||||||
|
|
||||||
|
case Utils::IO::ReadError::SizeMismatch:
|
||||||
|
LogMsg(message, Log::WARNING);
|
||||||
|
throw InternalServerErrorHTTPError(readResult.error().message);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw InternalServerErrorHTTPError(tr("Web server error. Unknown error."));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (file.size() > MAX_ALLOWED_FILESIZE)
|
QByteArray data = readResult.value();
|
||||||
{
|
|
||||||
qWarning("%s: exceeded the maximum allowed file size!", qUtf8Printable(path.toString()));
|
|
||||||
throw InternalServerErrorHTTPError(tr("Exceeded the maximum allowed file size (%1)!")
|
|
||||||
.arg(Utils::Misc::friendlyUnit(MAX_ALLOWED_FILESIZE)));
|
|
||||||
}
|
|
||||||
|
|
||||||
QByteArray data {file.readAll()};
|
|
||||||
file.close();
|
|
||||||
|
|
||||||
const QMimeType mimeType = QMimeDatabase().mimeTypeForFileNameAndData(path.data(), data);
|
const QMimeType mimeType = QMimeDatabase().mimeTypeForFileNameAndData(path.data(), data);
|
||||||
const bool isTranslatable = !m_isAltUIUsed && mimeType.inherits(u"text/plain"_qs);
|
const bool isTranslatable = !m_isAltUIUsed && mimeType.inherits(u"text/plain"_qs);
|
||||||
|
|
||||||
|
|
|
@ -28,14 +28,13 @@
|
||||||
|
|
||||||
#include "webui.h"
|
#include "webui.h"
|
||||||
|
|
||||||
#include <QFile>
|
|
||||||
|
|
||||||
#include "base/http/server.h"
|
#include "base/http/server.h"
|
||||||
#include "base/logger.h"
|
#include "base/logger.h"
|
||||||
#include "base/net/dnsupdater.h"
|
#include "base/net/dnsupdater.h"
|
||||||
#include "base/net/portforwarder.h"
|
#include "base/net/portforwarder.h"
|
||||||
#include "base/path.h"
|
#include "base/path.h"
|
||||||
#include "base/preferences.h"
|
#include "base/preferences.h"
|
||||||
|
#include "base/utils/io.h"
|
||||||
#include "base/utils/net.h"
|
#include "base/utils/net.h"
|
||||||
#include "webapplication.h"
|
#include "webapplication.h"
|
||||||
|
|
||||||
|
@ -85,10 +84,8 @@ void WebUI::configure()
|
||||||
{
|
{
|
||||||
const auto readData = [](const Path &path) -> QByteArray
|
const auto readData = [](const Path &path) -> QByteArray
|
||||||
{
|
{
|
||||||
QFile file {path.data()};
|
const auto readResult = Utils::IO::readFile(path, Utils::Net::MAX_SSL_FILE_SIZE);
|
||||||
if (!file.open(QIODevice::ReadOnly))
|
return readResult.value_or(QByteArray());
|
||||||
return {};
|
|
||||||
return file.read(Utils::Net::MAX_SSL_FILE_SIZE);
|
|
||||||
};
|
};
|
||||||
const QByteArray cert = readData(pref->getWebUIHttpsCertificatePath());
|
const QByteArray cert = readData(pref->getWebUIHttpsCertificatePath());
|
||||||
const QByteArray key = readData(pref->getWebUIHttpsKeyPath());
|
const QByteArray key = readData(pref->getWebUIHttpsKeyPath());
|
||||||
|
|
|
@ -17,6 +17,7 @@ set(testFiles
|
||||||
testutilscompare.cpp
|
testutilscompare.cpp
|
||||||
testutilsbytearray.cpp
|
testutilsbytearray.cpp
|
||||||
testutilsgzip.cpp
|
testutilsgzip.cpp
|
||||||
|
testutilsio.cpp
|
||||||
testutilsstring.cpp
|
testutilsstring.cpp
|
||||||
testutilsversion.cpp
|
testutilsversion.cpp
|
||||||
)
|
)
|
||||||
|
|
1
test/testdata/size10.txt
vendored
Normal file
1
test/testdata/size10.txt
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
123456789
|
115
test/testutilsio.cpp
Normal file
115
test/testutilsio.cpp
Normal file
|
@ -0,0 +1,115 @@
|
||||||
|
/*
|
||||||
|
* Bittorrent Client using Qt and libtorrent.
|
||||||
|
* Copyright (C) 2023 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 <QtGlobal>
|
||||||
|
#include <QTest>
|
||||||
|
|
||||||
|
#include "base/global.h"
|
||||||
|
#include "base/path.h"
|
||||||
|
#include "base/utils/io.h"
|
||||||
|
|
||||||
|
class TestUtilsIO final : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
Q_DISABLE_COPY_MOVE(TestUtilsIO)
|
||||||
|
|
||||||
|
public:
|
||||||
|
TestUtilsIO() = default;
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void testReadFile() const
|
||||||
|
{
|
||||||
|
const Path testFolder = Path(QString::fromUtf8(__FILE__)).parentPath() / Path(u"testdata"_qs);
|
||||||
|
|
||||||
|
const Path size10File = testFolder / Path(u"size10.txt"_qs);
|
||||||
|
const QByteArray size10Data = QByteArrayLiteral("123456789\n");
|
||||||
|
|
||||||
|
{
|
||||||
|
const auto readResult = Utils::IO::readFile(size10File, 0);
|
||||||
|
QCOMPARE(readResult.has_value(), false);
|
||||||
|
QCOMPARE(readResult.error().status, Utils::IO::ReadError::ExceedSize);
|
||||||
|
QCOMPARE(readResult.error().message.isEmpty(), false);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
const auto readResult = Utils::IO::readFile(size10File, 9);
|
||||||
|
QCOMPARE(readResult.has_value(), false);
|
||||||
|
QCOMPARE(readResult.error().status, Utils::IO::ReadError::ExceedSize);
|
||||||
|
QCOMPARE(readResult.error().message.isEmpty(), false);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
const auto readResult = Utils::IO::readFile(size10File, 10);
|
||||||
|
QCOMPARE(readResult.has_value(), true);
|
||||||
|
QCOMPARE(readResult.value(), size10Data);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
const auto readResult = Utils::IO::readFile(size10File, 11);
|
||||||
|
QCOMPARE(readResult.has_value(), true);
|
||||||
|
QCOMPARE(readResult.value(), size10Data);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
const auto readResult = Utils::IO::readFile(size10File, -1);
|
||||||
|
QCOMPARE(readResult.has_value(), true);
|
||||||
|
QCOMPARE(readResult.value(), size10Data);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const Path nonExistFile = testFolder / Path(u".non_existent_file_1234"_qs);
|
||||||
|
const auto readResult = Utils::IO::readFile(nonExistFile, 1);
|
||||||
|
QCOMPARE(readResult.has_value(), false);
|
||||||
|
QCOMPARE(readResult.error().status, Utils::IO::ReadError::NotExist);
|
||||||
|
QCOMPARE(readResult.error().message.isEmpty(), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef Q_OS_UNIX
|
||||||
|
{
|
||||||
|
const auto readResult = Utils::IO::readFile(Path(u"/dev/null"_qs), 10);
|
||||||
|
QCOMPARE(readResult.has_value(), true);
|
||||||
|
QCOMPARE(readResult.value().length(), 0);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
const auto readResult = Utils::IO::readFile(Path(u"/dev/null"_qs), -1);
|
||||||
|
QCOMPARE(readResult.has_value(), true);
|
||||||
|
QCOMPARE(readResult.value().length(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const auto readResult = Utils::IO::readFile(Path(u"/dev/zero"_qs), 10);
|
||||||
|
QCOMPARE(readResult.has_value(), true);
|
||||||
|
QCOMPARE(readResult.value().length(), 0);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
const auto readResult = Utils::IO::readFile(Path(u"/dev/zero"_qs), -1);
|
||||||
|
QCOMPARE(readResult.has_value(), true);
|
||||||
|
QCOMPARE(readResult.value().length(), 0);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
QTEST_APPLESS_MAIN(TestUtilsIO)
|
||||||
|
#include "testutilsio.moc"
|
Loading…
Add table
Add a link
Reference in a new issue