Files
pilotclient/src/blackmisc/network/networkutils.cpp
2024-10-12 22:16:16 +02:00

309 lines
10 KiB
C++

// SPDX-FileCopyrightText: Copyright (C) 2013 swift Project Community / Contributors
// SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-swift-pilot-client-1
#include "blackmisc/network/networkutils.h"
#include "blackmisc/network/server.h"
#include "blackmisc/eventloop.h"
#include "blackmisc/stringutils.h"
#include "blackconfig/buildconfig.h"
#include <QAbstractSocket>
#include <QDateTime>
#include <QMetaEnum>
#include <QHostAddress>
#include <QList>
#include <QNetworkInterface>
#include <QNetworkReply>
#include <QObject>
#include <QSignalMapper>
#include <QTcpSocket>
#include <QUrl>
#include <QUrlQuery>
#include <QStringBuilder>
#include <QVariant>
#include <QRegularExpression>
using namespace BlackConfig;
using namespace BlackMisc;
using namespace BlackMisc::Network;
namespace BlackMisc::Network
{
int CNetworkUtils::getTimeoutMs()
{
return 5000;
}
int CNetworkUtils::getLongTimeoutMs()
{
return 3 * getTimeoutMs();
}
QStringList CNetworkUtils::getKnownLocalIpV4Addresses()
{
QStringList ips;
const QList<QHostAddress> allAddresses = QNetworkInterface::allAddresses();
for (const QHostAddress &address : allAddresses)
{
if (address.isNull()) { continue; }
if (address.protocol() == QAbstractSocket::IPv4Protocol)
{
const QString a = address.toString();
ips.append(a);
}
}
ips.sort();
return ips;
}
bool CNetworkUtils::canConnect(const QString &hostAddress, int port, QString &message, int timeoutMs)
{
if (timeoutMs < 0) { timeoutMs = getTimeoutMs(); } // external functions might call with -1
QTcpSocket socket;
QSignalMapper mapper;
QObject::connect(&socket, &QTcpSocket::connected, &mapper, qOverload<>(&QSignalMapper::map));
QObject::connect(&socket, &QAbstractSocket::errorOccurred, &mapper, qOverload<>(&QSignalMapper::map));
mapper.setMapping(&socket, 0);
CEventLoop eventLoop;
eventLoop.stopWhen(&mapper, &QSignalMapper::mappedInt);
socket.connectToHost(hostAddress, static_cast<quint16>(port));
const bool timedOut = !eventLoop.exec(timeoutMs);
if (socket.state() != QTcpSocket::ConnectedState)
{
static const QString e("Connection failed: '%1'");
message = timedOut ? e.arg("Timed out") : e.arg(socket.errorString());
return false;
}
else
{
static const QString ok("OK, connected");
message = ok;
return true;
}
}
bool CNetworkUtils::canConnect(const Network::CServer &server, QString &message, int timeoutMs)
{
return CNetworkUtils::canConnect(server.getAddress(), server.getPort(), message, timeoutMs);
}
bool CNetworkUtils::canConnect(const QString &url, QString &message, int timeoutMs)
{
if (url.isEmpty())
{
message = QObject::tr("Missing URL", "BlackMisc");
return false;
}
return canConnect(QUrl(url), message, timeoutMs);
}
bool CNetworkUtils::canConnect(const QUrl &url, QString &message, int timeoutMs)
{
if (!url.isValid())
{
message = QObject::tr("Invalid URL: %1", "BlackMisc").arg(url.toString());
return false;
}
if (url.isRelative())
{
message = QObject::tr("Relative URL cannot be tested: %1", "BlackMisc").arg(url.toString());
return false;
}
const QString host(url.host());
const QString scheme(url.scheme().toLower());
int p = url.port();
if (p < 0)
{
p = scheme.contains("https") ? 443 : 80;
}
return canConnect(host, p, message, timeoutMs);
}
bool CNetworkUtils::canConnect(const QUrl &url, int timeoutMs)
{
QString m;
return canConnect(url, m, timeoutMs);
}
bool CNetworkUtils::isValidIPv4Address(const QString &candidate)
{
QHostAddress address(candidate);
return (QAbstractSocket::IPv4Protocol == address.protocol());
}
bool CNetworkUtils::isValidIPv6Address(const QString &candidate)
{
QHostAddress address(candidate);
return (QAbstractSocket::IPv6Protocol == address.protocol());
}
bool CNetworkUtils::isValidPort(const QString &port)
{
bool success;
int p = port.toInt(&success);
if (!success) return false;
return (p >= 1 && p <= 65535);
}
QString CNetworkUtils::buildUrl(const QString &protocol, const QString &server, const QString &baseUrl, const QString &serviceUrl)
{
Q_ASSERT_X(protocol.length() > 3, Q_FUNC_INFO, "worng protocol");
Q_ASSERT_X(!server.isEmpty(), Q_FUNC_INFO, "missing server");
Q_ASSERT_X(!serviceUrl.isEmpty(), Q_FUNC_INFO, "missing service URL");
QString url(server);
if (!baseUrl.isEmpty())
{
url.append("/").append(baseUrl);
}
url.append("/").append(serviceUrl);
url.replace("//", "/");
return protocol + "://" + url;
}
void CNetworkUtils::setSwiftUserAgent(QNetworkRequest &request, const QString &userAgentDetails)
{
static const QString defaultUserAgent("swift/" + CBuildConfig::getVersionString());
// User-Agent is known header, we use high level setHeader not setRawHeader
const QVariant agent = QVariant::fromValue(userAgentDetails.isEmpty() ? defaultUserAgent : defaultUserAgent + "/" + userAgentDetails);
request.setHeader(QNetworkRequest::UserAgentHeader, agent);
}
void CNetworkUtils::addDebugFlag(QUrlQuery &qurl)
{
qurl.addQueryItem("XDEBUG_SESSION_START", "ECLIPSE_DBGP");
}
QNetworkRequest CNetworkUtils::getSwiftNetworkRequest(const QUrl &url, RequestType type, const QString &userAgentDetails)
{
QNetworkRequest request(url);
switch (type)
{
case PostUrlEncoded:
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
break;
default:
break;
}
CNetworkUtils::setSwiftUserAgent(request, userAgentDetails);
return request;
}
QNetworkRequest CNetworkUtils::getSwiftNetworkRequest(const QNetworkRequest &request, const QString &userAgentDetails)
{
QNetworkRequest req(request); // copy
CNetworkUtils::setSwiftUserAgent(req, userAgentDetails);
return req;
}
qint64 CNetworkUtils::lastModifiedMsSinceEpoch(const QNetworkReply *nwReply)
{
Q_ASSERT(nwReply);
const QDateTime lm = CNetworkUtils::lastModifiedDateTime(nwReply);
return lm.isValid() ? lm.toMSecsSinceEpoch() : -1;
}
QDateTime CNetworkUtils::lastModifiedDateTime(const QNetworkReply *nwReply)
{
Q_ASSERT(nwReply);
const QVariant lastModifiedQv = nwReply->header(QNetworkRequest::LastModifiedHeader);
if (lastModifiedQv.isValid() && lastModifiedQv.canConvert<QDateTime>())
{
return lastModifiedQv.value<QDateTime>();
}
return QDateTime();
}
qint64 CNetworkUtils::lastModifiedSinceNow(const QNetworkReply *nwReply)
{
const qint64 sinceEpoch = CNetworkUtils::lastModifiedMsSinceEpoch(nwReply);
return sinceEpoch > 0 ? std::max(0LL, QDateTime::currentMSecsSinceEpoch() - sinceEpoch) : QDateTime::currentMSecsSinceEpoch();
}
qint64 CNetworkUtils::requestDuration(const QNetworkReply *nwReply)
{
if (!nwReply) { return -1; }
const QVariant started = nwReply->property("started");
if (started.isValid() && started.canConvert<qint64>())
{
const qint64 now = QDateTime::currentMSecsSinceEpoch();
const qint64 start = started.value<qint64>();
return (now - start);
}
return -1;
}
int CNetworkUtils::getHttpStatusCode(QNetworkReply *nwReply)
{
if (!nwReply) { return -1; }
const QVariant statusCode = nwReply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
if (!statusCode.isValid()) { return -1; }
const int status = statusCode.toInt();
return status;
}
bool CNetworkUtils::isHttpStatusRedirect(QNetworkReply *nwReply)
{
if (!nwReply) { return false; }
const int code = CNetworkUtils::getHttpStatusCode(nwReply);
return code == 301 || code == 302 || code == 303 || code == 307;
}
QUrl CNetworkUtils::getHttpRedirectUrl(QNetworkReply *nwReply)
{
if (!nwReply) { return QUrl(); }
const QVariant possibleRedirectUrl = nwReply->attribute(QNetworkRequest::RedirectionTargetAttribute);
if (!possibleRedirectUrl.isValid()) { return QUrl(); }
QUrl redirectUrl = possibleRedirectUrl.toUrl();
if (redirectUrl.isRelative())
{
redirectUrl = nwReply->url().resolved(redirectUrl);
}
return redirectUrl;
}
QString CNetworkUtils::removeHtmlPartsFromPhpErrorMessage(const QString &errorMessage)
{
if (errorMessage.isEmpty()) { return errorMessage; }
QString phpError(errorMessage);
thread_local const QRegularExpression regEx("<[^>]*>");
return phpError.remove(regEx);
}
bool CNetworkUtils::looksLikePhpErrorMessage(const QString &errorMessage)
{
if (errorMessage.length() < 50) { return false; }
if (errorMessage.contains("xdebug", Qt::CaseInsensitive)) { return true; }
return false;
}
const QString &CNetworkUtils::networkOperationToString(QNetworkAccessManager::Operation operation)
{
static const QString h("HEAD");
static const QString g("GET");
static const QString put("PUT");
static const QString d("DELETE");
static const QString post("POST");
static const QString c("custom");
static const QString u("unknown");
switch (operation)
{
case QNetworkAccessManager::HeadOperation: return h;
case QNetworkAccessManager::GetOperation: return g;
case QNetworkAccessManager::PutOperation: return put;
case QNetworkAccessManager::PostOperation: return post;
case QNetworkAccessManager::DeleteOperation: return d;
case QNetworkAccessManager::CustomOperation: return c;
case QNetworkAccessManager::UnknownOperation:
default:
break;
}
return u;
}
} // namespace