Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### 🔧 Changed

- Windows - Provide more error details if the UI fails to start because the system proxy is enabled (#2005).

- **Fixes:**
- Windows DDA Grabber - Prevent image updates when mouse is moved. Provide a Warning on incompatible setting. (#2002)
- EffectModule - Reference Counting (Use-After-Free) bugs (#2010) - _Thanks to @wr-web_
Expand All @@ -34,6 +36,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- EffectFileHandler: Refactor effect file management
- EffectEngine: Added dedicated `hyperion.effect` debug logging category
- Empty image consistency applied. 0×0 is now the canonical empty image; 1×1 is no longer treated as empty
- ProviderRestAPI - Add error when failing to load Qt SSL

## [2.2.1](https://github.com/hyperion-project/hyperion.ng/releases/tag/2.2.1) - 2026-04-06

Expand Down
50 changes: 45 additions & 5 deletions include/utils/NetUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,17 +32,57 @@ inline bool portAvailable(quint16& port, QSharedPointer<Logger> log)
{
const quint16 prevPort = port;
QTcpServer server;
int failCount = 0;

while (!server.listen(QHostAddress::Any, port))
{
Warning(log,"Port '%d' is already in use, will increment", port);
port ++;
++failCount;
QAbstractSocket::SocketError err = server.serverError();
QString errString = server.errorString();

if (failCount == 1)
{
Warning(log, "Port '%d' binding failed. SocketError: %d (%s)",
port, err, QSTRING_CSTR(errString));
}
else
{
Debug(log, "Port '%d' binding failed. SocketError: %d (%s)",
port, err, QSTRING_CSTR(errString));
}

// UnsupportedSocketOperationError typically means a system proxy rejected the
// bind (SOCKSv5 does not support listen). Aborting immediately if it occurs — no port will succeed.
if (err == QAbstractSocket::UnsupportedSocketOperationError)
{
Error(log, "Port '%d' bind rejected as unsupported operation (proxy?). Aborting port search.", port);
return false;
}

// On some platforms (notably Windows), certain ports fail with SocketAccessError
// due to OS/proxy reservations or exclusions while higher ports remain available.
if (err == QAbstractSocket::SocketAccessError)
{
Debug(log, "Port '%d' is reserved or excluded by the OS/Proxy, trying next port.", port);
}
Comment thread
Lord-Grey marked this conversation as resolved.

if (port >= MAX_PORT)
{
Error(log, "Reached maximum port limit (%d).", MAX_PORT);
return false;
}

port++;
}

// Explicitly release the port now that we proved we could listen on it.
server.close();
if(port != prevPort)

if (port != prevPort)
{
Warning(log, "The requested Port '%d' is already in use, will use Port '%d' instead", prevPort, port);
return false;
Warning(log, "Requested Port '%d' was unavailable, will use Port '%d' instead", prevPort, port);
}

return true;
}

Expand Down
74 changes: 52 additions & 22 deletions libsrc/leddevice/dev_net/ProviderRestApi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ ProviderRestApi::ProviderRestApi(const QString& scheme, const QString& host, int
: _log(Logger::getInstance("LEDDEVICE"))
, _networkManager(new QNetworkAccessManager())
, _requestTimeout(DEFAULT_REST_TIMEOUT)
, _isSelfSignedCertificateAccpeted(false)
, _isSelfSignedCertificateAccepted(false)
{
TRACK_SCOPE();

Expand Down Expand Up @@ -221,6 +221,13 @@ httpResponse ProviderRestApi::executeOperation(QNetworkAccessManager::Operation
request.setUrl(url);
request.setOriginatingObject(this);

#ifndef QT_NO_SSL
if (!_caCertificates.isEmpty() && url.scheme().compare("https", Qt::CaseInsensitive) == 0)
{
request.setSslConfiguration(_requestSslConfiguration);
}
#endif

#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0))
_networkManager->setTransferTimeout(static_cast<int>(_requestTimeout.count()));
#endif
Expand Down Expand Up @@ -282,7 +289,6 @@ httpResponse ProviderRestApi::executeOperation(QNetworkAccessManager::Operation

return response;
}

namespace {
QString getReplyErrorReason(QNetworkReply* const& reply, const httpResponse& response)
{
Expand Down Expand Up @@ -424,41 +430,65 @@ QByteArray httpResponse::getHeader(const QByteArray& header) const

bool ProviderRestApi::setCaCertificate(const QString& caFileName)
{
bool rc {false};
/// Add our own CA to the default SSL configuration
QSslConfiguration configuration = QSslConfiguration::defaultConfiguration();
#ifndef QT_NO_SSL
if (!QSslSocket::supportsSsl())
{
QString buildVersion = QSslSocket::sslLibraryBuildVersionString();
QString runtimeVersion = QSslSocket::sslLibraryVersionString();

if (buildVersion.isEmpty())
{
buildVersion = QStringLiteral("not available");
}

if (runtimeVersion.isEmpty())
{
runtimeVersion = QStringLiteral("not available");
}

Error(_log, "SSL support is compiled into Qt, but the underlying SSL libraries failed to load at runtime. "
"Built against: \"%s\", runtime version: \"%s\".",
QSTRING_CSTR(buildVersion),
QSTRING_CSTR(runtimeVersion));
return false;
}
#else
Error(_log, "SSL support is entirely disabled in this build of Qt (QT_NO_SSL is defined).");
return false;
#endif

QFile caFile (caFileName);
if (!caFile.open(QIODevice::ReadOnly))
{
Error(_log,"Unable to open CA-Certificate file: %s", QSTRING_CSTR(caFileName));
Error(_log, "Unable to open CA-Certificate file: %s", QSTRING_CSTR(caFileName));
return false;
}

QSslCertificate const cert (&caFile);
// Load all PEM certificates from the bundle (handles concatenated/multi-cert bundles)
QList<QSslCertificate> newCerts = QSslCertificate::fromDevice(&caFile, QSsl::Pem);
caFile.close();

QList<QSslCertificate> allowedCAs;
allowedCAs << cert;
configuration.setCaCertificates(allowedCAs);

QSslConfiguration::setDefaultConfiguration(configuration);

#ifndef QT_NO_SSL
if (QSslSocket::supportsSsl())
if (newCerts.isEmpty())
{
QObject::connect( _networkManager.get(), &QNetworkAccessManager::sslErrors, this, &ProviderRestApi::onSslErrors );
_networkManager->connectToHostEncrypted(_apiUrl.host(), static_cast<quint16>(_apiUrl.port()), configuration);
rc = true;
Error(_log, "The file %s does not contain any valid SSL certificates", QSTRING_CSTR(caFileName));
return false;
}
#endif

return rc;
_caCertificates = newCerts;

_requestSslConfiguration = QSslConfiguration::defaultConfiguration();
QList<QSslCertificate> allowedCAs = _requestSslConfiguration.caCertificates();
allowedCAs.append(_caCertificates);
_requestSslConfiguration.setCaCertificates(allowedCAs);

// Use Qt::UniqueConnection to prevent duplicate signal connections on repeated calls
QObject::connect( _networkManager.get(), &QNetworkAccessManager::sslErrors, this, &ProviderRestApi::onSslErrors, Qt::UniqueConnection );
return true;
}

void ProviderRestApi::acceptSelfSignedCertificates(bool isAccepted)
{
_isSelfSignedCertificateAccpeted = isAccepted;
_isSelfSignedCertificateAccepted = isAccepted;
}

void ProviderRestApi::setAlternateServerIdentity(const QString& serverIdentity)
Expand Down Expand Up @@ -563,7 +593,7 @@ bool ProviderRestApi::handleSslError(const QSslError& error, const QSslConfigura
}
break;
case QSslError::SelfSignedCertificate:
if (_isSelfSignedCertificateAccpeted)
if (_isSelfSignedCertificateAccepted)
{
const QSslCertificate& certificate = error.certificate();
if (matchesPinnedCertificate(certificate))
Expand Down
5 changes: 4 additions & 1 deletion libsrc/leddevice/dev_net/ProviderRestApi.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include <QNetworkRequest>
#include <QNetworkReply>
#include <QJsonDocument>
#include <QSslCertificate>
#include <QUrlQuery>

#include <QFile>
Expand Down Expand Up @@ -483,9 +484,11 @@ protected slots:
QUrlQuery _query;

QNetworkRequest _networkRequestHeaders;
QList<QSslCertificate> _caCertificates;
QSslConfiguration _requestSslConfiguration;

QString _serverIdentity;
bool _isSelfSignedCertificateAccpeted;
bool _isSelfSignedCertificateAccepted;
};

#endif // PROVIDERRESTAPI_H
Loading