release: 1.0.4 security hardening and stability fixes

Validate manual stream URLs (http/https only), restrict notification
cover downloads to HTTPS, and add transfer timeouts for metadata and
cover fetches. Document playback, tray, MPRIS, and QML refactor changes
in CHANGELOG and AppStream metadata.
This commit is contained in:
Sebastian Palencsar
2026-06-20 14:31:13 +02:00
parent cbd82e5ab1
commit 74cabebc4b
9 changed files with 91 additions and 2 deletions

View File

@@ -42,6 +42,7 @@ void CoverArtFetcher::fetch(const QString &artist, const QString &title)
url.setQuery(query);
QNetworkRequest request(url);
request.setTransferTimeout(10000);
m_currentReply = m_networkManager->get(request);
connect(m_currentReply, &QNetworkReply::finished, this, &CoverArtFetcher::onReplyFinished);
}

View File

@@ -30,6 +30,7 @@ void IcyReader::start(const QString &url)
request.setRawHeader("User-Agent", "VLC/3.0.16 LibVLC/3.0.16");
// Streams often use 302 redirects to load balancers/relays
request.setAttribute(QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::NoLessSafeRedirectPolicy);
request.setTransferTimeout(15000);
m_reply = m_nam->get(request);

View File

@@ -25,7 +25,7 @@ int main(int argc, char *argv[])
QApplication::setApplicationName(QStringLiteral("BearWave"));
QApplication::setDesktopFileName(QStringLiteral("de.nerdbear.bearwave"));
QApplication::setOrganizationName(QStringLiteral("BearWave"));
QApplication::setApplicationVersion(QStringLiteral("1.0.3"));
QApplication::setApplicationVersion(QStringLiteral("1.0.4"));
QApplication app(argc, argv);

View File

@@ -123,6 +123,11 @@ void NotificationManager::onTrackInfoChanged()
void NotificationManager::downloadCover(const QString &url)
{
if (!url.startsWith(QLatin1String("https://"))) {
qDebug() << "Skipping cover download for non-HTTPS URL";
return;
}
if (m_currentReply) {
QNetworkReply *reply = m_currentReply;
m_currentReply = nullptr;
@@ -133,6 +138,7 @@ void NotificationManager::downloadCover(const QString &url)
m_pendingCoverUrl = url;
QNetworkRequest request(url);
request.setTransferTimeout(10000);
m_currentReply = m_networkManager->get(request);
connect(m_currentReply, &QNetworkReply::finished, this, &NotificationManager::onDownloadFinished);
}

View File

@@ -12,6 +12,7 @@
#include <QJsonObject>
#include <QStandardPaths>
#include <QLocale>
#include <QUrl>
namespace {
constexpr int kRecentLimit = 20;
@@ -20,6 +21,16 @@ QString appConfigDir()
{
return QDir::homePath() + QStringLiteral("/.config/bearwave");
}
bool isAllowedStreamUrl(const QString &urlString)
{
const QUrl url(urlString.trimmed());
if (!url.isValid() || url.isEmpty()) {
return false;
}
const QString scheme = url.scheme().toLower();
return scheme == QStringLiteral("http") || scheme == QStringLiteral("https");
}
}
RadioBackend::RadioBackend(QObject *parent)
@@ -409,7 +420,12 @@ void RadioBackend::addManualStation(const QString &name, const QString &url, con
if (name.trimmed().isEmpty() || url.trimmed().isEmpty()) {
return;
}
if (!isAllowedStreamUrl(url)) {
setLastError(tr("Stream URL must use http:// or https://"));
return;
}
setLastError(QString());
RadioStation *station = new RadioStation(this);
station->setName(name.trimmed());
station->setUrl(url.trimmed());
@@ -848,7 +864,12 @@ void RadioBackend::editManualStation(QObject *stationObj, const QString &name, c
if (!station || name.trimmed().isEmpty() || url.trimmed().isEmpty()) {
return;
}
if (!isAllowedStreamUrl(url)) {
setLastError(tr("Stream URL must use http:// or https://"));
return;
}
setLastError(QString());
QString oldUrl = station->url();
QString oldUrlResolved = station->urlResolved();