Skip to content

Commit 4dea7c9

Browse files
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.
1 parent c117acc commit 4dea7c9

File tree

6 files changed

+63
-34
lines changed

6 files changed

+63
-34
lines changed

src/interfaces/node.h

+9
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@
2222
#include <tuple>
2323
#include <vector>
2424

25+
static const char DEFAULT_PROXY_HOST[] = "127.0.0.1";
26+
static constexpr uint16_t DEFAULT_PROXY_PORT = 9050;
27+
2528
class BanMan;
2629
class CFeeRate;
2730
class CNodeStats;
@@ -126,6 +129,12 @@ class Node
126129
//! Get proxy.
127130
virtual bool getProxy(Network net, Proxy& proxy_info) = 0;
128131

132+
//! Get default proxy address.
133+
virtual std::string defaultProxyAddress() = 0;
134+
135+
//! Validate a proxy address.
136+
virtual bool validateProxyAddress(const std::string& addr_port) = 0;
137+
129138
//! Get number of connections.
130139
virtual size_t getNodeCount(ConnectionDirection flags) = 0;
131140

src/node/interfaces.cpp

+22
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
#include <uint256.h>
4646
#include <univalue.h>
4747
#include <util/check.h>
48+
#include <util/strencodings.h>
4849
#include <util/translation.h>
4950
#include <validation.h>
5051
#include <validationinterface.h>
@@ -169,6 +170,27 @@ class NodeImpl : public Node
169170
}
170171
void mapPort(bool use_upnp, bool use_natpmp) override { StartMapPort(use_upnp, use_natpmp); }
171172
bool getProxy(Network net, Proxy& proxy_info) override { return GetProxy(net, proxy_info); }
173+
std::string defaultProxyAddress() override
174+
{
175+
return std::string(DEFAULT_PROXY_HOST) + ":" + ToString(DEFAULT_PROXY_PORT);
176+
}
177+
bool validateProxyAddress(const std::string& addr_port) override
178+
{
179+
uint16_t port{0};
180+
std::string hostname;
181+
// First, attempt to split the input address into hostname and port components.
182+
// We call SplitHostPort to validate that a port is provided in addr_port.
183+
// If either splitting fails or port is zero (not specified), return false.
184+
if (!SplitHostPort(addr_port, port, hostname) || !port) return false;
185+
186+
// Create a service endpoint (CService) from the address and port.
187+
// If port is missing in addr_port, DEFAULT_PROXY_PORT is used as the fallback.
188+
CService serv(LookupNumeric(addr_port, DEFAULT_PROXY_PORT));
189+
190+
// Construct the Proxy with the service endpoint and return if it's valid
191+
Proxy addrProxy = Proxy(serv, true);
192+
return addrProxy.IsValid();
193+
}
172194
size_t getNodeCount(ConnectionDirection flags) override
173195
{
174196
return m_context->connman ? m_context->connman->GetNodeCount(flags) : 0;

src/qml/components/ProxySettings.qml

+17-6
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,12 @@ import QtQuick.Controls 2.15
77
import QtQuick.Layouts 1.15
88
import "../controls"
99

10+
import org.bitcoincore.qt 1.0
11+
1012
ColumnLayout {
13+
property string ipAndPortHeader: qsTr("IP and Port")
14+
property string invalidIpError: qsTr("Invalid IP address or port format. Use '255.255.255.255:65535' or '[ffff::]:65535'")
15+
1116
spacing: 4
1217
Header {
1318
headerBold: true
@@ -41,14 +46,17 @@ ColumnLayout {
4146
Setting {
4247
id: defaultProxy
4348
Layout.fillWidth: true
44-
header: qsTr("IP and Port")
45-
errorText: qsTr("Invalid IP address or port format. Please use the format '255.255.255.255:65535'.")
49+
header: ipAndPortHeader
50+
errorText: invalidIpError
4651
state: !defaultProxyEnable.loadedItem.checked ? "DISABLED" : "FILLED"
4752
showErrorText: !defaultProxy.loadedItem.validInput && defaultProxyEnable.loadedItem.checked
4853
actionItem: IPAddressValueInput {
4954
parentState: defaultProxy.state
50-
description: "127.0.0.1:9050"
55+
description: nodeModel.defaultProxyAddress()
5156
activeFocusOnTab: true
57+
onTextChanged: {
58+
validInput = nodeModel.validateProxyAddress(text);
59+
}
5260
}
5361
onClicked: {
5462
loadedItem.filled = true
@@ -89,14 +97,17 @@ ColumnLayout {
8997
Setting {
9098
id: torProxy
9199
Layout.fillWidth: true
92-
header: qsTr("IP and Port")
93-
errorText: qsTr("Invalid IP address or port format. Please use the format '255.255.255.255:65535'.")
100+
header: ipAndPortHeader
101+
errorText: invalidIpError
94102
state: !torProxyEnable.loadedItem.checked ? "DISABLED" : "FILLED"
95103
showErrorText: !torProxy.loadedItem.validInput && torProxyEnable.loadedItem.checked
96104
actionItem: IPAddressValueInput {
97105
parentState: torProxy.state
98-
description: "127.0.0.1:9050"
106+
description: nodeModel.defaultProxyAddress()
99107
activeFocusOnTab: true
108+
onTextChanged: {
109+
validInput = nodeModel.validateProxyAddress(text);
110+
}
100111
}
101112
onClicked: {
102113
loadedItem.filled = true

src/qml/controls/IPAddressValueInput.qml

+2-28
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@ TextInput {
1616
property bool validInput: true
1717
enabled: true
1818
state: root.parentState
19-
validator: RegExpValidator { regExp: /[0-9.:]*/ } // Allow only digits, dots, and colons
19+
validator: RegularExpressionValidator { regularExpression: /^[\][0-9a-f.:]+$/i } // Allow only IPv4/ IPv6 chars
2020

21-
maximumLength: 21
21+
maximumLength: 47
2222

2323
states: [
2424
State {
@@ -53,30 +53,4 @@ TextInput {
5353
Behavior on color {
5454
ColorAnimation { duration: 150 }
5555
}
56-
57-
function isValidIPPort(input)
58-
{
59-
var parts = input.split(":");
60-
if (parts.length !== 2) return false;
61-
if (parts[1].length === 0) return false; // port part is empty
62-
var ipAddress = parts[0];
63-
var ipAddressParts = ipAddress.split(".");
64-
if (ipAddressParts.length !== 4) return false;
65-
for (var i = 0; (i < ipAddressParts.length); i++) {
66-
if (ipAddressParts[i].length === 0) return false; // ip group number part is empty
67-
if (parseInt(ipAddressParts[i]) > 255) return false;
68-
}
69-
var port = parseInt(parts[1]);
70-
if (port < 1 || port > 65535) return false;
71-
return true;
72-
}
73-
74-
// Connections element to ensure validation on editing finished
75-
Connections {
76-
target: root
77-
function onTextChanged() {
78-
// Validate the input whenever editing is finished
79-
validInput = isValidIPPort(root.text);
80-
}
81-
}
8256
}

src/qml/models/nodemodel.cpp

+10
Original file line numberDiff line numberDiff line change
@@ -166,3 +166,13 @@ void NodeModel::ConnectToNumConnectionsChangedSignal()
166166
setNumOutboundPeers(new_num_peers.outbound_full_relay + new_num_peers.block_relay);
167167
});
168168
}
169+
170+
bool NodeModel::validateProxyAddress(QString address_port)
171+
{
172+
return m_node.validateProxyAddress(address_port.toStdString());
173+
}
174+
175+
QString NodeModel::defaultProxyAddress()
176+
{
177+
return QString::fromStdString(m_node.defaultProxyAddress());
178+
}

src/qml/models/nodemodel.h

+3
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,9 @@ class NodeModel : public QObject
6262
void startShutdownPolling();
6363
void stopShutdownPolling();
6464

65+
Q_INVOKABLE bool validateProxyAddress(QString addr_port);
66+
Q_INVOKABLE QString defaultProxyAddress();
67+
6568
public Q_SLOTS:
6669
void initializeResult(bool success, interfaces::BlockAndHeaderTipInfo tip_info);
6770

0 commit comments

Comments
 (0)