Commit 1394a383 authored by Calin Culianu's avatar Calin Culianu
Browse files

Merge branch 'fix/validate_txviewer_urls' into 'master'

[backport] Transaction viewer URL validation

See merge request !1161
parents bf1ccf00 27206031
Pipeline #284567687 passed with stages
in 60 minutes and 18 seconds
......@@ -81,6 +81,12 @@ subobject instead.
...
## User interface changes
The transaction viewer (configurable via the Settings -> Display dialog)
configuration now accept only valid HTTP or HTTPS URLs.
Existing URLs that do not conform to these schemes are not displayed in
the context menu.
## Regressions
......
......@@ -213,7 +213,7 @@ void OptionsDialog::setModel(OptionsModel *_model) {
static_cast<void (QValueComboBox::*)()>(&QValueComboBox::valueChanged),
[this] { showRestartWarning(); });
connect(ui->thirdPartyTxUrls, &QLineEdit::textChanged,
[this] { showRestartWarning(); });
[this] { thirdPartyTxWarning(); });
}
void OptionsDialog::setCurrentTab(OptionsDialog::Tab tab) {
......@@ -349,6 +349,18 @@ void OptionsDialog::showRestartWarning(bool fPersistent) {
}
}
void OptionsDialog::thirdPartyTxWarning(bool fPersistent)
{
QString str = ui->thirdPartyTxUrls->displayText();
if (!OptionsModel::isValidThirdPartyTxUrlString(str)) {
ui->statusLabel->setStyleSheet("QLabel { color: red; }");
ui->statusLabel->setText(tr("Not a valid HTTP or HTTPS URL."));
} else { // It is a valid URL
showRestartWarning(fPersistent);
}
}
void OptionsDialog::clearStatusLabel() {
ui->statusLabel->clear();
if (model && model->isRestartRequired()) {
......
......@@ -58,6 +58,7 @@ private Q_SLOTS:
void togglePruneWarning(bool enabled);
void showRestartWarning(bool fPersistent = false);
void thirdPartyTxWarning(bool fPersistent = false);
void clearStatusLabel();
void updateProxyValidationState();
/* query the networks, for which the default proxy is used */
......
......@@ -363,6 +363,25 @@ QVariant OptionsModel::data(const QModelIndex &index, int role) const {
return QVariant();
}
bool OptionsModel::isValidThirdPartyTxUrlString(QString value)
{
// Check that the URLs are valid, and https or https.
// Requiring http(s) ensures that certain schemes that auto-execute
// cannot be used. Although the user would need to explicitly
// configure such to happen, preventing this configuration protects
// the average user.
QStringList listUrls = GUIUtil::splitSkipEmptyParts(value, "|");
for (auto &urlStr : listUrls) {
// Remove whitespace and replace our tx placeholder with some
// valid URL data for validity checking.
const QUrl url(urlStr.replace("%s", "tx").trimmed(), QUrl::StrictMode);
if (!url.isValid() || (url.scheme().toLower() != "https" && url.scheme().toLower() != "http")) {
return false;
}
}
return true;
}
// write QSettings values
bool OptionsModel::setData(const QModelIndex &index, const QVariant &value,
int role) {
......@@ -454,10 +473,11 @@ bool OptionsModel::setData(const QModelIndex &index, const QVariant &value,
break;
case ThirdPartyTxUrls:
if (strThirdPartyTxUrls != value.toString()) {
strThirdPartyTxUrls = value.toString();
settings.setValue("strThirdPartyTxUrls",
strThirdPartyTxUrls);
setRestartRequired(true);
if (isValidThirdPartyTxUrlString(value.toString())) {
strThirdPartyTxUrls = value.toString();
settings.setValue("strThirdPartyTxUrls", strThirdPartyTxUrls);
setRestartRequired(true);
}
}
break;
case Language:
......
......@@ -86,6 +86,9 @@ public:
void setRestartRequired(bool fRequired);
bool isRestartRequired() const;
// Returns false if this URL is invalid (malformed or not HTTP/HTTPS)
static bool isValidThirdPartyTxUrlString(QString value);
interfaces::Node &node() const { return m_node; }
private:
......
......@@ -8,6 +8,7 @@
#include <config.h>
#include <key_io.h>
#include <qt/guiutil.h>
#include <qt/optionsmodel.h>
namespace {
......@@ -25,6 +26,7 @@ private:
} // namespace
void GUIUtilTests::dummyAddressTest() {
GUIUtilTestConfig config;
const CChainParams &params = config.GetChainParams();
......@@ -89,3 +91,21 @@ void GUIUtilTests::formatBytesTest() {
QVERIFY(GUIUtil::formatBytes(1'999'999'999) == "1,99 GB");
QVERIFY(GUIUtil::formatBytes(2'000'000'000) == "2,00 GB");
}
void GUIUtilTests::txViewerURLValidationTest() {
QLocale::setDefault(QLocale("en_US"));
QVERIFY(OptionsModel::isValidThirdPartyTxUrlString("")); // special case of empty URL is allowed
QVERIFY(!OptionsModel::isValidThirdPartyTxUrlString("foo"));
QVERIFY(!OptionsModel::isValidThirdPartyTxUrlString("123.123.123.123"));
QVERIFY(OptionsModel::isValidThirdPartyTxUrlString("http://foo.com"));
QVERIFY(OptionsModel::isValidThirdPartyTxUrlString("http:/foo.com")); // ?
QVERIFY(OptionsModel::isValidThirdPartyTxUrlString("http://foo.com/%s"));
QVERIFY(OptionsModel::isValidThirdPartyTxUrlString("https://foo.com"));
QVERIFY(OptionsModel::isValidThirdPartyTxUrlString("https://foo.com/%s"));
QVERIFY(OptionsModel::isValidThirdPartyTxUrlString("https:/foo.com")); // ?
QVERIFY(!OptionsModel::isValidThirdPartyTxUrlString("ftp://foo.com/path"));
QVERIFY(!OptionsModel::isValidThirdPartyTxUrlString("ssh://foo.com/path"));
QVERIFY(OptionsModel::isValidThirdPartyTxUrlString("HTTP://foo.com/path"));
QVERIFY(OptionsModel::isValidThirdPartyTxUrlString("HTTPS://foo.com/path"));
}
......@@ -15,6 +15,7 @@ private Q_SLOTS:
void dummyAddressTest();
void toCurrentEncodingTest();
void formatBytesTest();
void txViewerURLValidationTest();
};
#endif // BITCOIN_QT_TEST_GUIUTILTESTS_H
......@@ -271,7 +271,7 @@ void TransactionView::setModel(WalletModel *_model) {
for (int i = 0; i < listUrls.size(); ++i) {
const auto url = listUrls[i].trimmed();
const auto host = QUrl(url, QUrl::StrictMode).host();
if (!host.isEmpty()) {
if (!host.isEmpty() && OptionsModel::isValidThirdPartyTxUrlString(url)) {
// use host as menu item label
QAction *thirdPartyTxUrlAction = new QAction(host, this);
if (i == 0) {
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment