Skip to content

Commit

Permalink
qml, proxy: Allow IPv6 and move out UI validation
Browse files Browse the repository at this point in the history
Allowed to configure the proxy with an IPv6 address and moved
the validation out from the UI/ control to the node interface.
  • Loading branch information
pablomartin4btc committed Nov 1, 2024
1 parent c117acc commit 0e181eb
Show file tree
Hide file tree
Showing 6 changed files with 52 additions and 33 deletions.
5 changes: 5 additions & 0 deletions src/interfaces/node.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@
#include <tuple>
#include <vector>

static const char DEFAULT_PROXY_HOST[] = "127.0.0.1";
static constexpr uint16_t DEFAULT_PROXY_PORT = 9050;

class BanMan;
class CFeeRate;
class CNodeStats;
Expand Down Expand Up @@ -268,6 +271,8 @@ class Node
//! accessible across processes.
virtual node::NodeContext* context() { return nullptr; }
virtual void setContext(node::NodeContext* context) { }
virtual bool validateProxyAddress(const std::string ipAddress) = 0;

This comment has been minimized.

Copy link
@johnny9

johnny9 Nov 2, 2024

Contributor

From what I understand, we should not be making any changes to code outside of the qml folder. So we should keep validation in UI controllers.

This comment has been minimized.

Copy link
@pablomartin4btc

pablomartin4btc Nov 4, 2024

Author Contributor

As we discussed offline, for validation that involves complex business logic within the core code, it’s preferable to use interfaces to access that logic rather than directly (as in current Qt) or duplicating it and maintaining it in the UI. This approach keeps the UI simpler and avoids redundancy. Any code added to these interfaces could later be merged upstream, unless we’re explicitly asked to open a PR there.

virtual std::string defaultProxyAddress() = 0;
};

//! Return implementation of Node interface.
Expand Down
16 changes: 16 additions & 0 deletions src/node/interfaces.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
#include <uint256.h>
#include <univalue.h>
#include <util/check.h>
#include <util/strencodings.h>
#include <util/translation.h>
#include <validation.h>
#include <validationinterface.h>
Expand Down Expand Up @@ -398,6 +399,21 @@ class NodeImpl : public Node
ArgsManager& args() { return *Assert(Assert(m_context)->args); }
ChainstateManager& chainman() { return *Assert(m_context->chainman); }
NodeContext* m_context{nullptr};
bool validateProxyAddress(const std::string ipAddress) override
{
uint16_t port{0};
std::string hostname;
if (!SplitHostPort(ipAddress, port, hostname) || !port) return false;

CService serv(LookupNumeric(ipAddress, DEFAULT_PROXY_PORT));

Proxy addrProxy = Proxy(serv, true);
return addrProxy.IsValid();
}
std::string defaultProxyAddress() override
{
return std::string(DEFAULT_PROXY_HOST) + ":" + ToString(DEFAULT_PROXY_PORT);
}
};

bool FillBlock(const CBlockIndex* index, const FoundBlock& block, UniqueLock<RecursiveMutex>& lock, const CChain& active, const BlockManager& blockman)
Expand Down
23 changes: 17 additions & 6 deletions src/qml/components/ProxySettings.qml
Original file line number Diff line number Diff line change
Expand Up @@ -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.")

spacing: 4
Header {
headerBold: true
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
28 changes: 1 addition & 27 deletions src/qml/controls/IPAddressValueInput.qml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ TextInput {
property bool validInput: true
enabled: true
state: root.parentState
validator: RegExpValidator { regExp: /[0-9.:]*/ } // Allow only digits, dots, and colons
validator: RegExpValidator { regExp: /[0-9A-Fa-f.:\[\]]*/ } // Allow only IPv4/ IPv6 chars

maximumLength: 21

Expand Down Expand Up @@ -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);
}
}
}
10 changes: 10 additions & 0 deletions src/qml/models/nodemodel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -166,3 +166,13 @@ void NodeModel::ConnectToNumConnectionsChangedSignal()
setNumOutboundPeers(new_num_peers.outbound_full_relay + new_num_peers.block_relay);
});
}

bool NodeModel::validateProxyAddress(QString ipAddress)
{
return m_node.validateProxyAddress(ipAddress.toStdString());
}

QString NodeModel::defaultProxyAddress()
{
return QString::fromStdString(m_node.defaultProxyAddress());
}
3 changes: 3 additions & 0 deletions src/qml/models/nodemodel.h
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ class NodeModel : public QObject
void startShutdownPolling();
void stopShutdownPolling();

Q_INVOKABLE bool validateProxyAddress(QString ipAddress);
Q_INVOKABLE QString defaultProxyAddress();

public Q_SLOTS:
void initializeResult(bool success, interfaces::BlockAndHeaderTipInfo tip_info);

Expand Down

0 comments on commit 0e181eb

Please sign in to comment.