From cb09713007f59c95514e3846cf3031767efb04f2 Mon Sep 17 00:00:00 2001 From: dranik Date: Wed, 17 Jun 2026 16:22:53 +0300 Subject: [PATCH] host/SNI/path validation backend + flow-default flag --- .../ui/models/protocols/xrayConfigModel.cpp | 94 ++++++++++++++++++- client/ui/models/protocols/xrayConfigModel.h | 7 +- 2 files changed, 97 insertions(+), 4 deletions(-) diff --git a/client/ui/models/protocols/xrayConfigModel.cpp b/client/ui/models/protocols/xrayConfigModel.cpp index 93c1ce902..5b19f2186 100644 --- a/client/ui/models/protocols/xrayConfigModel.cpp +++ b/client/ui/models/protocols/xrayConfigModel.cpp @@ -4,6 +4,10 @@ #include "core/protocols/protocolUtils.h" #include "core/utils/constants/configKeys.h" #include "core/utils/constants/protocolConstants.h" +#include "core/utils/networkUtilities.h" + +#include +#include using namespace amnezia; using namespace ProtocolUtils; @@ -272,7 +276,7 @@ void XrayConfigModel::updateModel(amnezia::DockerContainer container, const amne } if (!m_protocolConfig.serverConfig.isThirdPartyConfig) { - applyDefaultsToServerConfig(m_protocolConfig.serverConfig); + applyDefaultsToServerConfig(m_protocolConfig.serverConfig, false); } m_originalProtocolConfig = m_protocolConfig; @@ -283,7 +287,7 @@ void XrayConfigModel::updateModel(amnezia::DockerContainer container, const amne } } -void XrayConfigModel::applyDefaultsToServerConfig(amnezia::XrayServerConfig &config) +void XrayConfigModel::applyDefaultsToServerConfig(amnezia::XrayServerConfig &config, bool fillFlowDefault) { if (config.port.isEmpty()) { config.port = protocols::xray::defaultPort; @@ -306,7 +310,7 @@ void XrayConfigModel::applyDefaultsToServerConfig(amnezia::XrayServerConfig &con config.security = protocols::xray::defaultSecurity; } - if (config.flow.isEmpty()) { + if (fillFlowDefault && config.flow.isEmpty()) { config.flow = protocols::xray::defaultFlow; } @@ -585,3 +589,87 @@ QString XrayConfigModel::mkcpDefaultWriteBufferSize() { return QString::fromLatin1(protocols::xray::defaultMkcpWriteBufferSize); } + +namespace { + bool isValidSingleHost(const QString &t) + { + if (t.isEmpty() || t.length() > 253) { + return false; + } + QHostAddress a(t); + if (a.protocol() == QHostAddress::IPv4Protocol) { + return NetworkUtilities::checkIPv4Format(t); + } + if (a.protocol() == QHostAddress::IPv6Protocol) { + return true; + } + static const QRegularExpression onlyDigits(QStringLiteral(R"(^\d+$)")); + if (onlyDigits.match(t).hasMatch()) { + return false; + } + QRegExp re = NetworkUtilities::domainRegExp(); + re.setCaseSensitivity(Qt::CaseInsensitive); + return re.exactMatch(t); + } +} + +bool XrayConfigModel::isValidHost(const QString &host) +{ + const QString t = host.trimmed(); + if (t.isEmpty()) { + return true; + } + return isValidSingleHost(t); +} + +bool XrayConfigModel::isValidSni(const QString &sni) +{ + const QString t = sni.trimmed(); + if (t.isEmpty()) { + return true; + } + if (t.startsWith(QLatin1String("*."))) { + return isValidSingleHost(t.mid(2)); + } + return isValidSingleHost(t); +} + +bool XrayConfigModel::isValidPath(const QString &path) +{ + const QString t = path.trimmed(); + if (t.isEmpty()) { + return true; + } + return t.startsWith(QLatin1Char('/')); +} + +QStringList XrayConfigModel::validationErrors() const +{ + QStringList errs; + const auto &srv = m_protocolConfig.serverConfig; + + if (!srv.port.isEmpty()) { + bool ok = false; + const int p = srv.port.toInt(&ok); + if (!ok || p < 1 || p > 65535) { + errs << tr("Port must be in the range of 1 to 65535"); + } + } + + if (srv.security == QLatin1String("tls") || srv.security == QLatin1String("reality")) { + if (!isValidSni(srv.sni)) { + errs << tr("SNI: enter a valid IP address or domain name"); + } + } + + if (srv.transport == QLatin1String("xhttp")) { + if (!isValidHost(srv.xhttp.host)) { + errs << tr("Host: enter a valid IP address or domain name"); + } + if (!isValidPath(srv.xhttp.path)) { + errs << tr("Path must start with \"/\""); + } + } + + return errs; +} diff --git a/client/ui/models/protocols/xrayConfigModel.h b/client/ui/models/protocols/xrayConfigModel.h index fee066107..0cce69a57 100644 --- a/client/ui/models/protocols/xrayConfigModel.h +++ b/client/ui/models/protocols/xrayConfigModel.h @@ -118,6 +118,11 @@ public: Q_INVOKABLE static QString mkcpDefaultReadBufferSize(); Q_INVOKABLE static QString mkcpDefaultWriteBufferSize(); + Q_INVOKABLE static bool isValidHost(const QString &host); + Q_INVOKABLE static bool isValidSni(const QString &sni); + Q_INVOKABLE static bool isValidPath(const QString &path); + Q_INVOKABLE QStringList validationErrors() const; + public slots: void updateModel(amnezia::DockerContainer container, const amnezia::XrayProtocolConfig& protocolConfig); amnezia::XrayProtocolConfig getProtocolConfig(); @@ -137,7 +142,7 @@ private: amnezia::XrayProtocolConfig m_protocolConfig; amnezia::XrayProtocolConfig m_originalProtocolConfig; - void applyDefaultsToServerConfig(amnezia::XrayServerConfig& config); + void applyDefaultsToServerConfig(amnezia::XrayServerConfig& config, bool fillFlowDefault = true); }; #endif // XRAYCONFIGMODEL_H