Compare commits

...

8 Commits

Author SHA1 Message Date
Vladimir Golovnev
22df0b45c5 Backport changes to v5.1.x branch
PR #22905.
2025-07-01 17:18:53 +03:00
sledgehammer999
bb34444ddc Store version numbers in the appropriate type 2025-07-01 13:07:55 +03:00
sledgehammer999
dd5c934103 Add fallback to update mechanism
This brings a fallback version check to the update mechanism,
which should be as stable as it can be.
It will allow migrating to another primary mechanism without
having to have updated the older primary mechanism too.
2025-07-01 13:07:24 +03:00
Ryu481
3fca180e98 Make qBittorrent quit on MacOS with main window closed
Fixes the reported bug that you couldn't quit qBittorrent when the main window was closed on MacOS.

Closes #22849.
PR #22931.
2025-06-29 21:37:30 +03:00
Chocobo1
9b29d37d21 WebAPI: Trim leading whitespaces on Run External Program fields
Hacked qbt instances may contain malicious script placed in Run External Program and the script
will attempt to hide itself by adding a lot whitespaces at the start of the command string.
Users may mistake the field of being empty but is actually not.
So trim the leading whitespaces to easily expose the malicious script.

Note that GUI already trim the fields and only WebAPI doesn't trim them. This patch will unify
the behavior.
Related: https://github.com/qbittorrent/docker-qbittorrent-nox/issues/71#issuecomment-2993567440

PR #22939.
2025-06-29 21:37:09 +03:00
Vladimir Golovnev
206d5abf84 Don't expose palette colors in UI theme editor
PR #22923.
Fixes regression introduced by #22330.
2025-06-27 15:54:51 +03:00
Vladimir Golovnev
13282d94ef Don't ignore QFile::open() result
PR #22889.
Closes #22888.
2025-06-23 12:15:34 +03:00
Vladimir Golovnev
1daa42e4fe Find CorePrivate package with Qt >= 6.10
PR #22890.
Closes #22887.
2025-06-23 12:15:12 +03:00
9 changed files with 98 additions and 36 deletions

View File

@@ -47,6 +47,9 @@ find_package(Boost ${minBoostVersion} REQUIRED)
find_package(OpenSSL ${minOpenSSLVersion} REQUIRED)
find_package(ZLIB ${minZlibVersion} REQUIRED)
find_package(Qt6 ${minQt6Version} REQUIRED COMPONENTS Core Network Sql Xml LinguistTools)
if (Qt6_FOUND AND (Qt6_VERSION VERSION_GREATER_EQUAL 6.10))
find_package(Qt6 ${minQt6Version} REQUIRED COMPONENTS CorePrivate)
endif()
if (DBUS)
find_package(Qt6 ${minQt6Version} REQUIRED COMPONENTS DBus)
set_package_properties(Qt6DBus PROPERTIES

View File

@@ -487,14 +487,14 @@ void SearchPluginManager::updateNova()
const Path enginePath = engineLocation();
QFile packageFile {(enginePath / Path(u"__init__.py"_s)).data()};
packageFile.open(QIODevice::WriteOnly);
packageFile.close();
if (packageFile.open(QIODevice::WriteOnly))
packageFile.close();
Utils::Fs::mkdir(enginePath / Path(u"engines"_s));
QFile packageFile2 {(enginePath / Path(u"engines/__init__.py"_s)).data()};
packageFile2.open(QIODevice::WriteOnly);
packageFile2.close();
if (packageFile2.open(QIODevice::WriteOnly))
packageFile2.close();
// Copy search plugin files (if necessary)
const auto updateFile = [&enginePath](const Path &filename, const bool compareVersion)

View File

@@ -1161,7 +1161,7 @@ void MainWindow::closeEvent(QCloseEvent *e)
if (!m_forceExit)
{
hide();
e->accept();
e->ignore();
return;
}
#else
@@ -1660,11 +1660,11 @@ void MainWindow::handleUpdateCheckFinished(ProgramUpdater *updater, const bool i
updater->deleteLater();
};
const QString newVersion = updater->getNewVersion();
if (!newVersion.isEmpty())
const auto newVersion = updater->getNewVersion();
if (newVersion.isValid())
{
const QString msg {tr("A new version is available.") + u"<br/>"
+ tr("Do you want to download %1?").arg(newVersion) + u"<br/><br/>"
+ tr("Do you want to download %1?").arg(newVersion.toString()) + u"<br/><br/>"
+ u"<a href=\"https://www.qbittorrent.org/news\">%1</a>"_s.arg(tr("Open changelog..."))};
auto *msgBox = new QMessageBox {QMessageBox::Question, tr("qBittorrent Update Available"), msg
, (QMessageBox::Yes | QMessageBox::No), this};

View File

@@ -35,10 +35,13 @@
#include <QtSystemDetection>
#include <QDebug>
#include <QDesktopServices>
#include <QJsonDocument>
#include <QJsonValue>
#include <QRegularExpression>
#include <QXmlStreamReader>
#include "base/global.h"
#include "base/logger.h"
#include "base/net/downloadmanager.h"
#include "base/preferences.h"
#include "base/utils/version.h"
@@ -46,23 +49,20 @@
namespace
{
bool isVersionMoreRecent(const QString &remoteVersion)
bool isVersionMoreRecent(const ProgramUpdater::Version &remoteVersion)
{
using Version = Utils::Version<4, 3>;
const auto newVersion = Version::fromString(remoteVersion);
if (!newVersion.isValid())
if (!remoteVersion.isValid())
return false;
const Version currentVersion {QBT_VERSION_MAJOR, QBT_VERSION_MINOR, QBT_VERSION_BUGFIX, QBT_VERSION_BUILD};
if (newVersion == currentVersion)
const ProgramUpdater::Version currentVersion {QBT_VERSION_MAJOR, QBT_VERSION_MINOR, QBT_VERSION_BUGFIX, QBT_VERSION_BUILD};
if (remoteVersion == currentVersion)
{
const bool isDevVersion = QStringLiteral(QBT_VERSION_STATUS).contains(
QRegularExpression(u"(alpha|beta|rc)"_s));
if (isDevVersion)
return true;
}
return (newVersion > currentVersion);
return (remoteVersion > currentVersion);
}
QString buildVariant()
@@ -82,30 +82,34 @@ namespace
void ProgramUpdater::checkForUpdates() const
{
const auto USER_AGENT = QStringLiteral("qBittorrent/" QBT_VERSION_2 " ProgramUpdater (www.qbittorrent.org)");
const auto RSS_URL = u"https://www.fosshub.com/feed/5b8793a7f9ee5a5c3e97a3b2.xml"_s;
const auto FALLBACK_URL = u"https://www.qbittorrent.org/versions.json"_s;
// Don't change this User-Agent. In case our updater goes haywire,
// the filehost can identify it and contact us.
Net::DownloadManager::instance()->download(
Net::DownloadRequest(RSS_URL).userAgent(QStringLiteral("qBittorrent/" QBT_VERSION_2 " ProgramUpdater (www.qbittorrent.org)"))
Net::DownloadManager::instance()->download(Net::DownloadRequest(RSS_URL).userAgent(USER_AGENT)
, Preferences::instance()->useProxyForGeneralPurposes(), this, &ProgramUpdater::rssDownloadFinished);
Net::DownloadManager::instance()->download(Net::DownloadRequest(FALLBACK_URL).userAgent(USER_AGENT)
, Preferences::instance()->useProxyForGeneralPurposes(), this, &ProgramUpdater::fallbackDownloadFinished);
m_hasCompletedOneReq = false;
}
QString ProgramUpdater::getNewVersion() const
ProgramUpdater::Version ProgramUpdater::getNewVersion() const
{
return m_newVersion;
return shouldUseFallback() ? m_fallbackRemoteVersion : m_remoteVersion;
}
void ProgramUpdater::rssDownloadFinished(const Net::DownloadResult &result)
{
if (result.status != Net::DownloadStatus::Success)
{
qDebug() << "Downloading the new qBittorrent updates RSS failed:" << result.errorString;
emit updateCheckFinished();
LogMsg(tr("Failed to download the update info. URL: %1. Error: %2").arg(result.url, result.errorString) , Log::WARNING);
handleFinishedRequest();
return;
}
qDebug("Finished downloading the new qBittorrent updates RSS");
const auto getStringValue = [](QXmlStreamReader &xml) -> QString
{
xml.readNext();
@@ -146,9 +150,10 @@ void ProgramUpdater::rssDownloadFinished(const Net::DownloadResult &result)
if (!version.isEmpty())
{
qDebug("Detected version is %s", qUtf8Printable(version));
if (isVersionMoreRecent(version))
const ProgramUpdater::Version tmpVer {version};
if (isVersionMoreRecent(tmpVer))
{
m_newVersion = version;
m_remoteVersion = tmpVer;
m_updateURL = updateLink;
}
}
@@ -163,10 +168,50 @@ void ProgramUpdater::rssDownloadFinished(const Net::DownloadResult &result)
}
}
emit updateCheckFinished();
handleFinishedRequest();
}
void ProgramUpdater::fallbackDownloadFinished(const Net::DownloadResult &result)
{
if (result.status != Net::DownloadStatus::Success)
{
LogMsg(tr("Failed to download the update info. URL: %1. Error: %2").arg(result.url, result.errorString) , Log::WARNING);
handleFinishedRequest();
return;
}
const auto json = QJsonDocument::fromJson(result.data);
#if defined(Q_OS_MACOS)
const QString platformKey = u"macos"_s;
#elif defined(Q_OS_WIN)
const QString platformKey = u"win"_s;
#endif
if (const QJsonValue verJSON = json[platformKey][u"version"_s]; verJSON.isString())
{
const ProgramUpdater::Version tmpVer {verJSON.toString()};
if (isVersionMoreRecent(tmpVer))
m_fallbackRemoteVersion = tmpVer;
}
handleFinishedRequest();
}
bool ProgramUpdater::updateProgram() const
{
return QDesktopServices::openUrl(m_updateURL);
return QDesktopServices::openUrl(shouldUseFallback() ? u"https://www.qbittorrent.org/download"_s : m_updateURL);
}
void ProgramUpdater::handleFinishedRequest()
{
if (m_hasCompletedOneReq)
emit updateCheckFinished();
else
m_hasCompletedOneReq = true;
}
bool ProgramUpdater::shouldUseFallback() const
{
return m_fallbackRemoteVersion > m_remoteVersion;
}

View File

@@ -30,9 +30,10 @@
#pragma once
#include <QObject>
#include <QString>
#include <QUrl>
#include "base/utils/version.h"
namespace Net
{
struct DownloadResult;
@@ -45,9 +46,10 @@ class ProgramUpdater final : public QObject
public:
using QObject::QObject;
using Version = Utils::Version<4, 3>;
void checkForUpdates() const;
QString getNewVersion() const;
Version getNewVersion() const;
bool updateProgram() const;
signals:
@@ -55,8 +57,14 @@ signals:
private slots:
void rssDownloadFinished(const Net::DownloadResult &result);
void fallbackDownloadFinished(const Net::DownloadResult &result);
private:
QString m_newVersion;
void handleFinishedRequest();
bool shouldUseFallback() const;
mutable bool m_hasCompletedOneReq = false;
Version m_remoteVersion;
Version m_fallbackRemoteVersion;
QUrl m_updateURL;
};

View File

@@ -80,8 +80,14 @@ inline QHash<QString, UIThemeColor> defaultUIThemeColors()
{u"TransferList.StoppedUploading"_s, {Color::Primer::Light::doneFg, Color::Primer::Dark::doneFg}},
{u"TransferList.Moving"_s, {Color::Primer::Light::successFg, Color::Primer::Dark::successFg}},
{u"TransferList.MissingFiles"_s, {Color::Primer::Light::dangerFg, Color::Primer::Dark::dangerFg}},
{u"TransferList.Error"_s, {Color::Primer::Light::dangerFg, Color::Primer::Dark::dangerFg}},
{u"TransferList.Error"_s, {Color::Primer::Light::dangerFg, Color::Primer::Dark::dangerFg}}
};
}
// Palette isn't customizable in default theme
inline QHash<QString, UIThemeColor> defaultPaletteColors()
{
return {
{u"Palette.Window"_s, {{}, {}}},
{u"Palette.WindowText"_s, {{}, {}}},
{u"Palette.Base"_s, {{}, {}}},

View File

@@ -273,8 +273,6 @@ void UIThemeDialog::loadColors()
int row = 2;
for (const QString &id : colorIDs)
{
if (id == u"Log.Normal")
qDebug() << "!!!!!!!";
m_ui->colorsLayout->addWidget(new QLabel(id), row, 0);
const UIThemeColor &defaultColor = defaultColors.value(id);

View File

@@ -105,6 +105,8 @@ DefaultThemeSource::DefaultThemeSource()
, m_colors {defaultUIThemeColors()}
{
loadColors();
// Palette isn't customizable in default theme
m_colors.insert(defaultPaletteColors());
}
QByteArray DefaultThemeSource::readStyleSheet()

View File

@@ -673,12 +673,12 @@ void AppController::setPreferencesAction()
if (hasKey(u"autorun_on_torrent_added_enabled"_s))
pref->setAutoRunOnTorrentAddedEnabled(it.value().toBool());
if (hasKey(u"autorun_on_torrent_added_program"_s))
pref->setAutoRunOnTorrentAddedProgram(it.value().toString());
pref->setAutoRunOnTorrentAddedProgram(it.value().toString().trimmed());
// Run an external program on torrent finished
if (hasKey(u"autorun_enabled"_s))
pref->setAutoRunOnTorrentFinishedEnabled(it.value().toBool());
if (hasKey(u"autorun_program"_s))
pref->setAutoRunOnTorrentFinishedProgram(it.value().toString());
pref->setAutoRunOnTorrentFinishedProgram(it.value().toString().trimmed());
// Connection
// Listening Port