From 4dea7c91a537710f3582d57b44f8908767ebb944 Mon Sep 17 00:00:00 2001 From: pablomartin4btc Date: Thu, 31 Oct 2024 10:29:08 -0300 Subject: [PATCH] qml, proxy: Allow IPv6 and move out UI validation Allowed to configure the proxy with an IPv6 address and moved the validation out from the UI/ control to the node interface. --- src/interfaces/node.h | 9 +++++++ src/node/interfaces.cpp | 22 +++++++++++++++++ src/qml/components/ProxySettings.qml | 23 +++++++++++++----- src/qml/controls/IPAddressValueInput.qml | 30 ++---------------------- src/qml/models/nodemodel.cpp | 10 ++++++++ src/qml/models/nodemodel.h | 3 +++ 6 files changed, 63 insertions(+), 34 deletions(-) diff --git a/src/interfaces/node.h b/src/interfaces/node.h index f6c79f0c1b..1aab36927b 100644 --- a/src/interfaces/node.h +++ b/src/interfaces/node.h @@ -22,6 +22,9 @@ #include #include +static const char DEFAULT_PROXY_HOST[] = "127.0.0.1"; +static constexpr uint16_t DEFAULT_PROXY_PORT = 9050; + class BanMan; class CFeeRate; class CNodeStats; @@ -126,6 +129,12 @@ class Node //! Get proxy. virtual bool getProxy(Network net, Proxy& proxy_info) = 0; + //! Get default proxy address. + virtual std::string defaultProxyAddress() = 0; + + //! Validate a proxy address. + virtual bool validateProxyAddress(const std::string& addr_port) = 0; + //! Get number of connections. virtual size_t getNodeCount(ConnectionDirection flags) = 0; diff --git a/src/node/interfaces.cpp b/src/node/interfaces.cpp index f1fe42206e..3dd867cb8d 100644 --- a/src/node/interfaces.cpp +++ b/src/node/interfaces.cpp @@ -45,6 +45,7 @@ #include #include #include +#include #include #include #include @@ -169,6 +170,27 @@ class NodeImpl : public Node } void mapPort(bool use_upnp, bool use_natpmp) override { StartMapPort(use_upnp, use_natpmp); } bool getProxy(Network net, Proxy& proxy_info) override { return GetProxy(net, proxy_info); } + std::string defaultProxyAddress() override + { + return std::string(DEFAULT_PROXY_HOST) + ":" + ToString(DEFAULT_PROXY_PORT); + } + bool validateProxyAddress(const std::string& addr_port) override + { + uint16_t port{0}; + std::string hostname; + // First, attempt to split the input address into hostname and port components. + // We call SplitHostPort to validate that a port is provided in addr_port. + // If either splitting fails or port is zero (not specified), return false. + if (!SplitHostPort(addr_port, port, hostname) || !port) return false; + + // Create a service endpoint (CService) from the address and port. + // If port is missing in addr_port, DEFAULT_PROXY_PORT is used as the fallback. + CService serv(LookupNumeric(addr_port, DEFAULT_PROXY_PORT)); + + // Construct the Proxy with the service endpoint and return if it's valid + Proxy addrProxy = Proxy(serv, true); + return addrProxy.IsValid(); + } size_t getNodeCount(ConnectionDirection flags) override { return m_context->connman ? m_context->connman->GetNodeCount(flags) : 0; diff --git a/src/qml/components/ProxySettings.qml b/src/qml/components/ProxySettings.qml index bc1467d0ff..6af147dd09 100644 --- a/src/qml/components/ProxySettings.qml +++ b/src/qml/components/ProxySettings.qml @@ -7,7 +7,12 @@ import QtQuick.Controls 2.15 import QtQuick.Layouts 1.15 import "../controls" +import org.bitcoincore.qt 1.0 + ColumnLayout { + property string ipAndPortHeader: qsTr("IP and Port") + property string invalidIpError: qsTr("Invalid IP address or port format. Use '255.255.255.255:65535' or '[ffff::]:65535'") + spacing: 4 Header { headerBold: true @@ -41,14 +46,17 @@ ColumnLayout { Setting { id: defaultProxy Layout.fillWidth: true - header: qsTr("IP and Port") - errorText: qsTr("Invalid IP address or port format. Please use the format '255.255.255.255:65535'.") + header: ipAndPortHeader + errorText: invalidIpError state: !defaultProxyEnable.loadedItem.checked ? "DISABLED" : "FILLED" showErrorText: !defaultProxy.loadedItem.validInput && defaultProxyEnable.loadedItem.checked actionItem: IPAddressValueInput { parentState: defaultProxy.state - description: "127.0.0.1:9050" + description: nodeModel.defaultProxyAddress() activeFocusOnTab: true + onTextChanged: { + validInput = nodeModel.validateProxyAddress(text); + } } onClicked: { loadedItem.filled = true @@ -89,14 +97,17 @@ ColumnLayout { Setting { id: torProxy Layout.fillWidth: true - header: qsTr("IP and Port") - errorText: qsTr("Invalid IP address or port format. Please use the format '255.255.255.255:65535'.") + header: ipAndPortHeader + errorText: invalidIpError state: !torProxyEnable.loadedItem.checked ? "DISABLED" : "FILLED" showErrorText: !torProxy.loadedItem.validInput && torProxyEnable.loadedItem.checked actionItem: IPAddressValueInput { parentState: torProxy.state - description: "127.0.0.1:9050" + description: nodeModel.defaultProxyAddress() activeFocusOnTab: true + onTextChanged: { + validInput = nodeModel.validateProxyAddress(text); + } } onClicked: { loadedItem.filled = true diff --git a/src/qml/controls/IPAddressValueInput.qml b/src/qml/controls/IPAddressValueInput.qml index d2ce4c7f16..01e9aa9021 100644 --- a/src/qml/controls/IPAddressValueInput.qml +++ b/src/qml/controls/IPAddressValueInput.qml @@ -16,9 +16,9 @@ TextInput { property bool validInput: true enabled: true state: root.parentState - validator: RegExpValidator { regExp: /[0-9.:]*/ } // Allow only digits, dots, and colons + validator: RegularExpressionValidator { regularExpression: /^[\][0-9a-f.:]+$/i } // Allow only IPv4/ IPv6 chars - maximumLength: 21 + maximumLength: 47 states: [ State { @@ -53,30 +53,4 @@ TextInput { Behavior on color { ColorAnimation { duration: 150 } } - - function isValidIPPort(input) - { - var parts = input.split(":"); - if (parts.length !== 2) return false; - if (parts[1].length === 0) return false; // port part is empty - var ipAddress = parts[0]; - var ipAddressParts = ipAddress.split("."); - if (ipAddressParts.length !== 4) return false; - for (var i = 0; (i < ipAddressParts.length); i++) { - if (ipAddressParts[i].length === 0) return false; // ip group number part is empty - if (parseInt(ipAddressParts[i]) > 255) return false; - } - var port = parseInt(parts[1]); - if (port < 1 || port > 65535) return false; - return true; - } - - // Connections element to ensure validation on editing finished - Connections { - target: root - function onTextChanged() { - // Validate the input whenever editing is finished - validInput = isValidIPPort(root.text); - } - } } diff --git a/src/qml/models/nodemodel.cpp b/src/qml/models/nodemodel.cpp index 521e5fa1c5..8ccc532016 100644 --- a/src/qml/models/nodemodel.cpp +++ b/src/qml/models/nodemodel.cpp @@ -166,3 +166,13 @@ void NodeModel::ConnectToNumConnectionsChangedSignal() setNumOutboundPeers(new_num_peers.outbound_full_relay + new_num_peers.block_relay); }); } + +bool NodeModel::validateProxyAddress(QString address_port) +{ + return m_node.validateProxyAddress(address_port.toStdString()); +} + +QString NodeModel::defaultProxyAddress() +{ + return QString::fromStdString(m_node.defaultProxyAddress()); +} diff --git a/src/qml/models/nodemodel.h b/src/qml/models/nodemodel.h index a17f9b0833..8603c44d36 100644 --- a/src/qml/models/nodemodel.h +++ b/src/qml/models/nodemodel.h @@ -62,6 +62,9 @@ class NodeModel : public QObject void startShutdownPolling(); void stopShutdownPolling(); + Q_INVOKABLE bool validateProxyAddress(QString addr_port); + Q_INVOKABLE QString defaultProxyAddress(); + public Q_SLOTS: void initializeResult(bool success, interfaces::BlockAndHeaderTipInfo tip_info);