fix Public host|Port|Promoted tag|FakeTLS domain|Internal/External IP

This commit is contained in:
dranik
2026-06-15 19:03:15 +03:00
parent b5c047565c
commit 0d99459670
4 changed files with 497 additions and 13 deletions
@@ -271,6 +271,7 @@ namespace amnezia
constexpr char workersModeAuto[] = "auto"; constexpr char workersModeAuto[] = "auto";
constexpr char workersModeManual[] = "manual"; constexpr char workersModeManual[] = "manual";
constexpr int maxWorkers = 32; constexpr int maxWorkers = 32;
constexpr int botTagHexLength = 32;
} }
} // namespace protocols } // namespace protocols
+322 -7
View File
@@ -1,7 +1,13 @@
#include "telemtConfigModel.h" #include "telemtConfigModel.h"
#include <QRegularExpression> #include "ui/models/utils/mtproxy_public_host_input.h"
#include <QHostAddress>
#include <QRegExp>
#include <QRegularExpression>
#include <qqml.h>
#include "core/utils/networkUtilities.h"
#include "core/utils/qrCodeUtils.h" #include "core/utils/qrCodeUtils.h"
#include "core/utils/constants/configKeys.h" #include "core/utils/constants/configKeys.h"
#include "core/utils/constants/protocolConstants.h" #include "core/utils/constants/protocolConstants.h"
@@ -9,7 +15,9 @@
using namespace amnezia; using namespace amnezia;
TelemtConfigModel::TelemtConfigModel(QObject *parent) : QAbstractListModel(parent) {} TelemtConfigModel::TelemtConfigModel(QObject *parent) : QAbstractListModel(parent) {
qmlRegisterType<PublicHostInputValidator>("TelemtConfig", 1, 0, "PublicHostInputValidator");
}
void TelemtConfigModel::applyDefaults(TelemtProtocolConfig &c) { void TelemtConfigModel::applyDefaults(TelemtProtocolConfig &c) {
if (c.port.isEmpty()) { if (c.port.isEmpty()) {
@@ -49,7 +57,11 @@ bool TelemtConfigModel::setData(const QModelIndex &index, const QVariant &value,
break; break;
} }
case Roles::TagRole: { case Roles::TagRole: {
m_protocolConfig.tag = value.toString(); const QString tag = sanitizeMtProxyTagFieldText(value.toString());
if (!isValidMtProxyTag(tag)) {
return false;
}
m_protocolConfig.tag = tag;
break; break;
} }
case Roles::IsEnabledRole: { case Roles::IsEnabledRole: {
@@ -57,7 +69,11 @@ bool TelemtConfigModel::setData(const QModelIndex &index, const QVariant &value,
break; break;
} }
case Roles::PublicHostRole: { case Roles::PublicHostRole: {
m_protocolConfig.publicHost = value.toString(); const QString h = value.toString().trimmed();
if (!isValidPublicHost(h)) {
return false;
}
m_protocolConfig.publicHost = h;
break; break;
} }
case Roles::TransportModeRole: { case Roles::TransportModeRole: {
@@ -65,7 +81,11 @@ bool TelemtConfigModel::setData(const QModelIndex &index, const QVariant &value,
break; break;
} }
case Roles::TlsDomainRole: { case Roles::TlsDomainRole: {
m_protocolConfig.tlsDomain = value.toString(); const QString d = value.toString().trimmed();
if (!isValidFakeTlsDomain(d)) {
return false;
}
m_protocolConfig.tlsDomain = d;
break; break;
} }
case Roles::AdditionalSecretsRole: { case Roles::AdditionalSecretsRole: {
@@ -85,11 +105,19 @@ bool TelemtConfigModel::setData(const QModelIndex &index, const QVariant &value,
break; break;
} }
case Roles::NatInternalIpRole: { case Roles::NatInternalIpRole: {
m_protocolConfig.natInternalIp = value.toString(); const QString ip = value.toString().trimmed();
if (!isValidOptionalIpv4(ip)) {
return false;
}
m_protocolConfig.natInternalIp = ip;
break; break;
} }
case Roles::NatExternalIpRole: { case Roles::NatExternalIpRole: {
m_protocolConfig.natExternalIp = value.toString(); const QString ip = value.toString().trimmed();
if (!isValidOptionalIpv4(ip)) {
return false;
}
m_protocolConfig.natExternalIp = ip;
break; break;
} }
case Roles::MaskEnabledRole: { case Roles::MaskEnabledRole: {
@@ -379,6 +407,293 @@ QString TelemtConfigModel::workersModeManual() const {
return QString::fromUtf8(protocols::telemt::workersModeManual); return QString::fromUtf8(protocols::telemt::workersModeManual);
} }
bool TelemtConfigModel::isValidPublicHost(const QString &host) const {
const QString t = host.trimmed();
if (t.isEmpty()) {
return true;
}
if (t.length() > 253) {
return false;
}
QHostAddress a(t);
if (a.protocol() == QHostAddress::IPv4Protocol) {
return NetworkUtilities::checkIPv4Format(t);
}
if (a.protocol() == QHostAddress::IPv6Protocol) {
// Reject unusable special addresses such as "::" (any), loopback and null.
if (a.isNull() || a.isLoopback() || a == QHostAddress(QHostAddress::AnyIPv6)) {
return false;
}
return true;
}
static const QRegularExpression onlyAsciiDigits(QStringLiteral(R"(^\d+$)"));
if (onlyAsciiDigits.match(t).hasMatch()) {
return false;
}
return NetworkUtilities::domainRegExp().exactMatch(t);
}
bool TelemtConfigModel::isPublicHostInputAllowed(const QString &text) const {
return mtproxyPublicHostInputAllowed(text);
}
bool TelemtConfigModel::isPublicHostTypingIncomplete(const QString &text) const {
const QString t = text.trimmed();
if (isValidPublicHost(t)) {
return false;
}
static const QRegularExpression onlyDigitDot(QStringLiteral(R"(^[0-9.]+$)"));
if (onlyDigitDot.match(t).hasMatch()) {
if (t.endsWith(QLatin1Char('.'))) {
return true;
}
const QStringList parts = t.split(QLatin1Char('.'), Qt::KeepEmptyParts);
if (parts.size() < 4) {
return true;
}
for (const QString &part: parts) {
if (part.isEmpty()) {
return true;
}
}
return false;
}
if (t.contains(QLatin1Char(':'))) {
if (t.contains(QLatin1String(":::"))) {
return false;
}
if (t.endsWith(QLatin1Char(':'))) {
return true;
}
QHostAddress a(t);
if (a.protocol() == QHostAddress::IPv6Protocol) {
return false;
}
if (!t.contains(QLatin1String("::")) && t.count(QLatin1Char(':')) < 7 && !t.contains(QLatin1Char('.'))) {
return true;
}
return false;
}
if (!t.contains(QLatin1Char('.'))) {
return true;
}
return false;
}
bool TelemtConfigModel::isValidMtProxyTag(const QString &tag) const {
if (tag.isEmpty()) {
return true;
}
static const QRegularExpression re(
QStringLiteral("^([0-9a-fA-F]{%1})$").arg(protocols::telemt::botTagHexLength));
return re.match(tag).hasMatch();
}
bool TelemtConfigModel::isMtProxyTagTypingIncomplete(const QString &text) const {
const QString t = text.trimmed();
if (t.isEmpty()) {
return true;
}
static const QRegularExpression hexOnly(QStringLiteral(R"(^[0-9a-fA-F]*$)"));
if (!hexOnly.match(t).hasMatch()) {
return false;
}
return t.size() < protocols::telemt::botTagHexLength;
}
int TelemtConfigModel::mtProxyBotTagHexLength() const {
return protocols::telemt::botTagHexLength;
}
bool TelemtConfigModel::isValidFakeTlsDomain(const QString &domain) const {
const QString t = domain.trimmed();
if (t.isEmpty()) {
return true;
}
if (t.length() > 253) {
return false;
}
QHostAddress addr;
if (addr.setAddress(t)) {
return false;
}
static const QRegularExpression onlyAsciiDigits(QStringLiteral(R"(^\d+$)"));
if (onlyAsciiDigits.match(t).hasMatch()) {
return false;
}
QRegExp re(NetworkUtilities::domainRegExp());
re.setCaseSensitivity(Qt::CaseInsensitive);
if (!re.exactMatch(t)) {
return false;
}
// ee + 32 hex (base secret) + hex(UTF-8 domain); keep headroom under typical client limits.
if (t.toUtf8().size() > 111) {
return false;
}
return true;
}
QString TelemtConfigModel::normalizeFakeTlsDomainInput(const QString &input) const {
QString t = input.trimmed();
if (t.startsWith(QLatin1String("https://"), Qt::CaseInsensitive)) {
t = t.mid(8);
} else if (t.startsWith(QLatin1String("http://"), Qt::CaseInsensitive)) {
t = t.mid(7);
}
if (const int slash = t.indexOf(QLatin1Char('/')); slash >= 0) {
t = t.left(slash);
}
if (const int at = t.indexOf(QLatin1Char('@')); at >= 0) {
t = t.mid(at + 1);
}
if (const int colon = t.indexOf(QLatin1Char(':')); colon >= 0) {
t = t.left(colon);
}
if (t.startsWith(QLatin1String("www."), Qt::CaseInsensitive)) {
const QString rest = t.mid(4);
if (rest.contains(QLatin1Char('.'))) {
t = rest;
}
}
return t.trimmed();
}
bool TelemtConfigModel::isFakeTlsDomainTypingIncomplete(const QString &text) const {
const QString t = text.trimmed();
if (t.isEmpty()) {
return true;
}
if (isValidFakeTlsDomain(t)) {
return false;
}
if (t.contains(QLatin1Char('/')) || t.contains(QLatin1Char(':')) || t.contains(QLatin1Char('@'))
|| t.contains(QLatin1Char(' '))) {
return false;
}
if (t.contains(QLatin1String(".."))) {
return false;
}
if (!t.contains(QLatin1Char('.'))) {
return true;
}
if (t.endsWith(QLatin1Char('.'))) {
return true;
}
static const QRegularExpression legalPartial(QStringLiteral(R"(^[a-zA-Z0-9.-]*$)"));
if (!legalPartial.match(t).hasMatch()) {
return false;
}
return true;
}
bool TelemtConfigModel::isFakeTlsDomainInputAllowed(const QString &text) const {
if (text.length() > 253) {
return false;
}
static const QRegularExpression re(QStringLiteral(R"(^[a-zA-Z0-9.-]*$)"));
return re.match(text).hasMatch();
}
QString TelemtConfigModel::sanitizeFakeTlsDomainFieldText(const QString &input) const {
const QString t = normalizeFakeTlsDomainInput(input);
QString out;
out.reserve(t.size());
for (const QChar &c: t) {
const ushort u = c.unicode();
const bool letter = (u >= 'a' && u <= 'z') || (u >= 'A' && u <= 'Z');
const bool digit = (u >= '0' && u <= '9');
if (letter || digit || u == '.' || u == '-') {
out.append(c);
}
}
if (out.size() > 253) {
out.truncate(253);
}
return out;
}
QString TelemtConfigModel::sanitizePublicHostFieldText(const QString &input) const {
QString out;
const int cap = qMin(input.size(), 253);
out.reserve(cap);
for (const QChar &c: input) {
if (out.size() >= 253) {
break;
}
const ushort u = c.unicode();
if ((u >= 'a' && u <= 'z') || (u >= 'A' && u <= 'Z') || (u >= '0' && u <= '9') || u == '.' || u == ':' ||
u == '-') {
out.append(c);
}
}
return out;
}
QString TelemtConfigModel::sanitizePortFieldText(const QString &input) const {
QString out;
out.reserve(qMin(input.size(), 5));
for (const QChar &c: input) {
const ushort u = c.unicode();
if (u >= '0' && u <= '9' && out.size() < 5) {
out.append(c);
}
}
return out;
}
QString TelemtConfigModel::sanitizeMtProxyTagFieldText(const QString &input) const {
QString trimmed = input.trimmed();
if (trimmed.startsWith(QLatin1String("0x"), Qt::CaseInsensitive)) {
trimmed = trimmed.mid(2).trimmed();
}
// Prefer a contiguous 32-hex run (paste from bot message with extra text).
static const QRegularExpression runHex(QStringLiteral(R"(([0-9a-fA-F]{32}))"));
const QRegularExpressionMatch m = runHex.match(trimmed);
if (m.hasMatch()) {
return m.captured(1);
}
const int cap = protocols::telemt::botTagHexLength;
QString out;
out.reserve(qMin(trimmed.size(), cap));
for (const QChar &c: trimmed) {
if (out.size() >= cap) {
break;
}
const ushort u = c.unicode();
if ((u >= '0' && u <= '9') || (u >= 'a' && u <= 'f') || (u >= 'A' && u <= 'F')) {
out.append(c);
}
}
return out;
}
QString TelemtConfigModel::sanitizeOptionalIpv4FieldText(const QString &input) const {
QString out;
out.reserve(qMin(input.size(), 15));
for (const QChar &c: input) {
if (out.size() >= 15) {
break;
}
const ushort u = c.unicode();
if ((u >= '0' && u <= '9') || u == '.') {
out.append(c);
}
}
return out;
}
bool TelemtConfigModel::isValidOptionalIpv4(const QString &ip) const {
const QString t = ip.trimmed();
if (t.isEmpty()) {
return true;
}
return NetworkUtilities::checkIPv4Format(t);
}
QHash<int, QByteArray> TelemtConfigModel::roleNames() const { QHash<int, QByteArray> TelemtConfigModel::roleNames() const {
QHash<int, QByteArray> roles; QHash<int, QByteArray> roles;
@@ -116,12 +116,44 @@ public slots:
Q_INVOKABLE QString workersModeManual() const; Q_INVOKABLE QString workersModeManual() const;
Q_INVOKABLE bool isValidPublicHost(const QString &host) const;
Q_INVOKABLE bool isPublicHostInputAllowed(const QString &text) const;
Q_INVOKABLE bool isPublicHostTypingIncomplete(const QString &text) const;
Q_INVOKABLE bool isValidMtProxyTag(const QString &tag) const;
Q_INVOKABLE bool isMtProxyTagTypingIncomplete(const QString &text) const;
Q_INVOKABLE int mtProxyBotTagHexLength() const;
Q_INVOKABLE bool isValidFakeTlsDomain(const QString &domain) const;
Q_INVOKABLE bool isFakeTlsDomainTypingIncomplete(const QString &text) const;
Q_INVOKABLE bool isFakeTlsDomainInputAllowed(const QString &text) const;
Q_INVOKABLE QString sanitizeFakeTlsDomainFieldText(const QString &input) const;
Q_INVOKABLE QString sanitizePublicHostFieldText(const QString &input) const;
Q_INVOKABLE QString sanitizePortFieldText(const QString &input) const;
Q_INVOKABLE QString sanitizeMtProxyTagFieldText(const QString &input) const;
Q_INVOKABLE QString sanitizeOptionalIpv4FieldText(const QString &input) const;
Q_INVOKABLE bool isValidOptionalIpv4(const QString &ip) const;
protected: protected:
QHash<int, QByteArray> roleNames() const override; QHash<int, QByteArray> roleNames() const override;
private: private:
static void applyDefaults(amnezia::TelemtProtocolConfig &c); static void applyDefaults(amnezia::TelemtProtocolConfig &c);
QString normalizeFakeTlsDomainInput(const QString &input) const;
amnezia::DockerContainer m_container = amnezia::DockerContainer::None; amnezia::DockerContainer m_container = amnezia::DockerContainer::None;
QJsonObject m_fullConfig; QJsonObject m_fullConfig;
amnezia::TelemtProtocolConfig m_protocolConfig; amnezia::TelemtProtocolConfig m_protocolConfig;
@@ -8,6 +8,7 @@ import PageEnum 1.0
import ContainerProps 1.0 import ContainerProps 1.0
import ProtocolEnum 1.0 import ProtocolEnum 1.0
import Style 1.0 import Style 1.0
import TelemtConfig 1.0
import "./" import "./"
import "../Controls2" import "../Controls2"
@@ -41,6 +42,35 @@ PageType {
property string savedTlsDomain: "" property string savedTlsDomain: ""
property string savedPublicHost: "" property string savedPublicHost: ""
readonly property var natIpv4InputFormat: /^(\d{1,3}\.){0,3}\d{0,3}$/
function natIpv4FieldShowInvalidError(text) {
var t = text ? String(text).replace(/^\s+|\s+$/g, '') : ""
if (t === "")
return false
if (TelemtConfigModel.isValidOptionalIpv4(t))
return false
var parts = t.split('.')
var j
for (j = 0; j < parts.length; j++) {
if (parts[j].length > 3)
return true
}
if (parts.length > 4)
return true
if (t.indexOf('.') < 0 && t.length > 3)
return true
if (t.endsWith('.'))
return false
if (parts.length < 4)
return false
for (var i = 0; i < parts.length; i++) {
if (parts[i] === "")
return true
}
return true
}
onSavedTransportModeChanged: { onSavedTransportModeChanged: {
if (savedTransportMode === "faketls") { if (savedTransportMode === "faketls") {
root.syncedSecretTabIndex = 1 root.syncedSecretTabIndex = 1
@@ -889,8 +919,26 @@ PageType {
headerText: qsTr("Public host / IP") headerText: qsTr("Public host / IP")
textField.placeholderText: ServersUiController.serverHostName(ServersUiController.processedServerId) textField.placeholderText: ServersUiController.serverHostName(ServersUiController.processedServerId)
textField.text: publicHost textField.text: publicHost
textField.maximumLength: 253
textField.validator: PublicHostInputValidator {
}
textField.onTextChanged: {
var t = publicHostTextField.textField.text
if (TelemtConfigModel.isPublicHostTypingIncomplete(t)) {
publicHostTextField.errorText = ""
} else if (!TelemtConfigModel.isValidPublicHost(t)) {
publicHostTextField.errorText = qsTr("Enter a valid IP address or domain name")
} else {
publicHostTextField.errorText = ""
}
}
textField.onEditingFinished: { textField.onEditingFinished: {
textField.text = textField.text.replace(/^\s+|\s+$/g, '') textField.text = textField.text.replace(/^\s+|\s+$/g, '')
if (!TelemtConfigModel.isValidPublicHost(textField.text)) {
publicHostTextField.errorText = qsTr("Enter a valid IP address or domain name")
return
}
publicHostTextField.errorText = ""
if (textField.text !== publicHost) { if (textField.text !== publicHost) {
publicHost = textField.text publicHost = textField.text
TelemtConfigModel.setPublicHost(publicHost) TelemtConfigModel.setPublicHost(publicHost)
@@ -932,6 +980,7 @@ PageType {
headerText: qsTr("Server port") headerText: qsTr("Server port")
textField.placeholderText: TelemtConfigModel.defaultPort() textField.placeholderText: TelemtConfigModel.defaultPort()
textField.maximumLength: 5 textField.maximumLength: 5
textField.inputMethodHints: Qt.ImhDigitsOnly
textField.validator: IntValidator { textField.validator: IntValidator {
bottom: 1 bottom: 1
top: 65535 top: 65535
@@ -940,8 +989,16 @@ PageType {
var savedPort = port var savedPort = port
textField.text = (savedPort === TelemtConfigModel.defaultPort()) ? "" : savedPort textField.text = (savedPort === TelemtConfigModel.defaultPort()) ? "" : savedPort
} }
textField.onTextChanged: {
var cur = portTextField.textField.text
var clean = TelemtConfigModel.sanitizePortFieldText(cur)
if (clean !== cur) {
textField.text = clean
textField.cursorPosition = clean.length
}
}
textField.onEditingFinished: { textField.onEditingFinished: {
textField.text = textField.text.replace(/^\s+|\s+$/g, '') textField.text = TelemtConfigModel.sanitizePortFieldText(textField.text)
var portValue = textField.text === "" ? TelemtConfigModel.defaultPort() : textField.text var portValue = textField.text === "" ? TelemtConfigModel.defaultPort() : textField.text
if (portValue !== port) { if (portValue !== port) {
port = portValue port = portValue
@@ -969,13 +1026,43 @@ PageType {
Layout.rightMargin: 16 Layout.rightMargin: 16
Layout.bottomMargin: 16 Layout.bottomMargin: 16
headerText: qsTr("Promoted channel tag (optional)") headerText: qsTr("Promoted channel tag (optional)")
textField.placeholderText: qsTr("leave empty if not needed") textField.placeholderText: qsTr("32 hex chars from @MTProxyBot (e.g. 3b7b2fa9…)")
textField.text: tag textField.text: tag
textField.maximumLength: 64 textField.maximumLength: TelemtConfigModel.mtProxyBotTagHexLength()
textField.onTextChanged: {
var cur = tagTextField.textField.text
var clean = TelemtConfigModel.sanitizeMtProxyTagFieldText(cur)
if (clean !== cur) {
textField.text = clean
textField.cursorPosition = clean.length
return
}
var tt = tagTextField.textField.text
if (tt === "") {
tagTextField.errorText = ""
return
}
if (TelemtConfigModel.isMtProxyTagTypingIncomplete(tt)) {
tagTextField.errorText = ""
return
}
if (!TelemtConfigModel.isValidMtProxyTag(tt)) {
tagTextField.errorText = qsTr("Proxy tag must be exactly 32 hexadecimal characters (0-9, A-F).")
return
}
tagTextField.errorText = ""
}
textField.onEditingFinished: { textField.onEditingFinished: {
textField.text = textField.text.replace(/^\s+|\s+$/g, '') var raw = textField.text.replace(/^\s+|\s+$/g, '')
if (textField.text !== tag) { var normalized = TelemtConfigModel.sanitizeMtProxyTagFieldText(raw)
tag = textField.text textField.text = normalized
if (!TelemtConfigModel.isValidMtProxyTag(normalized)) {
tagTextField.errorText = qsTr("Proxy tag must be exactly 32 hexadecimal characters (0-9, A-F). Leave empty if unused.")
return
}
tagTextField.errorText = ""
if (normalized !== tag) {
tag = normalized
TelemtConfigModel.setTag(tag) TelemtConfigModel.setTag(tag)
} }
} }
@@ -1059,13 +1146,30 @@ PageType {
visible: transportMode === "faketls" visible: transportMode === "faketls"
headerText: qsTr("FakeTLS domain") headerText: qsTr("FakeTLS domain")
textField.placeholderText: root.previousTlsDomain textField.placeholderText: root.previousTlsDomain
textField.validator: RegularExpressionValidator {
regularExpression: /^[A-Za-z0-9.-]*$/
}
Component.onCompleted: { Component.onCompleted: {
var savedDomain = tlsDomain var savedDomain = tlsDomain
textField.text = (savedDomain === TelemtConfigModel.defaultTlsDomain() || savedDomain === "") ? "" : savedDomain textField.text = (savedDomain === TelemtConfigModel.defaultTlsDomain() || savedDomain === "") ? "" : savedDomain
} }
textField.onTextChanged: {
var t = tlsDomainTextField.textField.text
if (t === "" || TelemtConfigModel.isFakeTlsDomainTypingIncomplete(t)
|| TelemtConfigModel.isValidFakeTlsDomain(t)) {
tlsDomainTextField.errorText = ""
} else {
tlsDomainTextField.errorText = qsTr("Enter a valid domain name")
}
}
textField.onEditingFinished: { textField.onEditingFinished: {
textField.text = textField.text.replace(/^\s+|\s+$/g, '') textField.text = textField.text.replace(/^\s+|\s+$/g, '')
var domainValue = textField.text === "" ? TelemtConfigModel.defaultTlsDomain() : textField.text var domainValue = textField.text === "" ? TelemtConfigModel.defaultTlsDomain() : textField.text
if (!TelemtConfigModel.isValidFakeTlsDomain(domainValue)) {
tlsDomainTextField.errorText = qsTr("Enter a valid domain name")
return
}
tlsDomainTextField.errorText = ""
if (domainValue !== tlsDomain) { if (domainValue !== tlsDomain) {
tlsDomain = domainValue tlsDomain = domainValue
TelemtConfigModel.setTlsDomain(tlsDomain) TelemtConfigModel.setTlsDomain(tlsDomain)
@@ -1323,8 +1427,24 @@ PageType {
headerText: qsTr("Internal IP") headerText: qsTr("Internal IP")
textField.placeholderText: "172.17.0.2" textField.placeholderText: "172.17.0.2"
textField.text: natInternalIp textField.text: natInternalIp
textField.maximumLength: 15
textField.validator: RegularExpressionValidator {
regularExpression: root.natIpv4InputFormat
}
textField.onTextChanged: {
if (root.natIpv4FieldShowInvalidError(textField.text)) {
natInternalIpTextField.errorText = qsTr("Enter a valid IPv4 address")
} else {
natInternalIpTextField.errorText = ""
}
}
textField.onEditingFinished: { textField.onEditingFinished: {
textField.text = textField.text.replace(/^\s+|\s+$/g, '') textField.text = textField.text.replace(/^\s+|\s+$/g, '')
if (!TelemtConfigModel.isValidOptionalIpv4(textField.text)) {
natInternalIpTextField.errorText = qsTr("Enter a valid IPv4 address")
return
}
natInternalIpTextField.errorText = ""
if (textField.text !== natInternalIp) { if (textField.text !== natInternalIp) {
natInternalIp = textField.text natInternalIp = textField.text
TelemtConfigModel.setNatInternalIp(natInternalIp) TelemtConfigModel.setNatInternalIp(natInternalIp)
@@ -1342,8 +1462,24 @@ PageType {
headerText: qsTr("External IP") headerText: qsTr("External IP")
textField.placeholderText: "1.2.3.4" textField.placeholderText: "1.2.3.4"
textField.text: natExternalIp textField.text: natExternalIp
textField.maximumLength: 15
textField.validator: RegularExpressionValidator {
regularExpression: root.natIpv4InputFormat
}
textField.onTextChanged: {
if (root.natIpv4FieldShowInvalidError(textField.text)) {
natExternalIpTextField.errorText = qsTr("Enter a valid IPv4 address")
} else {
natExternalIpTextField.errorText = ""
}
}
textField.onEditingFinished: { textField.onEditingFinished: {
textField.text = textField.text.replace(/^\s+|\s+$/g, '') textField.text = textField.text.replace(/^\s+|\s+$/g, '')
if (!TelemtConfigModel.isValidOptionalIpv4(textField.text)) {
natExternalIpTextField.errorText = qsTr("Enter a valid IPv4 address")
return
}
natExternalIpTextField.errorText = ""
if (textField.text !== natExternalIp) { if (textField.text !== natExternalIp) {
natExternalIp = textField.text natExternalIp = textField.text
TelemtConfigModel.setNatExternalIp(natExternalIp) TelemtConfigModel.setNatExternalIp(natExternalIp)